diff options
author | The Qt Project <gerrit-noreply@qt-project.org> | 2020-07-27 14:47:46 +0000 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2020-07-27 14:47:46 +0000 |
commit | 34349e6574500e087ced56cc9504431b3e3b6469 (patch) | |
tree | d1e822daf5382d536d3b2c8940152526fd2a8f04 | |
parent | 31a841ade7b1a3b10d5a132cc58a476295e2e0b5 (diff) | |
parent | eb6adb2a65902ca507dd1fdcd97fde333f324068 (diff) |
Merge "Merge branch 1.17 into master"
83 files changed, 2326 insertions, 65 deletions
diff --git a/.travis.yml b/.travis.yml index 096b24e2c..25a23279e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -83,11 +83,13 @@ jobs: addons: homebrew: packages: + - capnp - ccache - grpc - icoutils - makensis - protobuf + - python3 - p7zip update: true env: @@ -100,7 +102,7 @@ jobs: 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 - - pip3 install --user beautifulsoup4 lxml + - python3 -m pip install --user beautifulsoup4 lxml before_script: - ulimit -c unlimited -S # enable core dumps script: diff --git a/doc/howtos.qdoc b/doc/howtos.qdoc index 696c444cb..7e01624a0 100644 --- a/doc/howtos.qdoc +++ b/doc/howtos.qdoc @@ -40,6 +40,7 @@ \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?} @@ -237,6 +238,42 @@ } \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 diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc index 128c4d831..854a96aca 100644 --- a/doc/qbs.qdoc +++ b/doc/qbs.qdoc @@ -1525,18 +1525,47 @@ \endcode In this example, we want to install a couple of QML files and an executable. - The actual installation is then done like this (using the default profile): + When building, \QBS installs artifacts into the default root folder, namely + \c{<build root>/install-root}. The \l{qbs::installPrefix}{qbs.installPrefix} and + \l{qbs::installDir}{qbs.installDir} properties are appended to the root folder. \code - qbs --clean-install-root qbs.installRoot:/tmp/myProjectRoot + $ qbs build qbs.installPrefix:/usr \endcode + In this example, the executable will be installed into the \c{<build root>/install-root/usr/bin} + folder and the QML files will be installed into the + \c{<build root>/install-root/usr/share/myproject} folder. - Here, we want the \c installDir properties from the project file to be - interpreted relative to the directory \c{/tmp/myProjectRoot}, and we want - that directory to be removed first. + To skip installation during the build, use the \c --no-install option. - If the \l{qbs::installRoot}{qbs.installRoot} property is not given, a - default is used, namely \c{<build root>/install-root}. + 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 + # 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}. diff --git a/doc/reference/modules/capnprotocpp-module.qdoc b/doc/reference/modules/capnprotocpp-module.qdoc new file mode 100644 index 000000000..b041670ad --- /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 <foo.capnp.h> + + int main(int argc, char* argv[]) { + ::capnp::MallocMessageBuilder message; + + auto foo = message.initRoot<Foo>(); + 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 index 3c1744e09..f5f43f0f9 100644 --- a/doc/reference/modules/cpp-module.qdoc +++ b/doc/reference/modules/cpp-module.qdoc @@ -1370,6 +1370,8 @@ \l{cpp::}{systemRunPaths} are ignored. \nodefaultvalue + + \sa{How do I make use of rpaths?} */ /*! @@ -1406,6 +1408,8 @@ install names. \nodefaultvalue + + \sa{How do I make use of rpaths?} */ /*! diff --git a/doc/reference/modules/qbs-module.qdoc b/doc/reference/modules/qbs-module.qdoc index de7f9ae6e..e72df697c 100644 --- a/doc/reference/modules/qbs-module.qdoc +++ b/doc/reference/modules/qbs-module.qdoc @@ -280,7 +280,7 @@ \sa {Target Platforms} - \nodefaultvalue + \defaultvalue \l{qbs::hostPlatform}{hostPlatform} */ /*! diff --git a/examples/capnproto/addressbook_cpp/addressbook.capnp b/examples/capnproto/addressbook_cpp/addressbook.capnp new file mode 100644 index 000000000..1a6c60937 --- /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 000000000..b2bece947 --- /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 <capnp/message.h> +#include <capnp/serialize-packed.h> +#include <iostream> + +using addressbook::Person; +using addressbook::AddressBook; + +void writeAddressBook(int fd) { + ::capnp::MallocMessageBuilder message; + + AddressBook::Builder addressBook = message.initRoot<AddressBook>(); + ::capnp::List<Person>::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<Person::PhoneNumber>::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<AddressBook>(); + + 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 <capnp/message.h> +#include <capnp/serialize-packed.h> +#include <iostream> +#include <capnp/schema.h> +#include <capnp/dynamic.h> + +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<DynamicStruct>(schema); + + DynamicList::Builder people = + addressBook.init("people", 2).as<DynamicList>(); + + DynamicStruct::Builder alice = + people[0].as<DynamicStruct>(); + alice.set("id", 123); + alice.set("name", "Alice"); + alice.set("email", "alice@example.com"); + auto alicePhones = alice.init("phones", 1).as<DynamicList>(); + auto phone0 = alicePhones[0].as<DynamicStruct>(); + phone0.set("number", "555-1212"); + phone0.set("type", "mobile"); + alice.get("employment").as<DynamicStruct>() + .set("school", "MIT"); + + auto bob = people[1].as<DynamicStruct>(); + 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<T>()! + List<Person::PhoneNumber>::Builder bobPhones = + bob.init("phones", 2).as<List<Person::PhoneNumber>>(); + 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<DynamicStruct>() + .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<bool>() ? "true" : "false"); + break; + case DynamicValue::INT: + std::cout << value.as<int64_t>(); + break; + case DynamicValue::UINT: + std::cout << value.as<uint64_t>(); + break; + case DynamicValue::FLOAT: + std::cout << value.as<double>(); + break; + case DynamicValue::TEXT: + std::cout << '\"' << value.as<Text>().cStr() << '\"'; + break; + case DynamicValue::LIST: { + std::cout << "["; + bool first = true; + for (auto element: value.as<DynamicList>()) { + if (first) { + first = false; + } else { + std::cout << ", "; + } + dynamicPrintValue(element); + } + std::cout << "]"; + break; + } + case DynamicValue::ENUM: { + auto enumValue = value.as<DynamicEnum>(); + 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<DynamicStruct>(); + 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<DynamicStruct>(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<AddressBook>(); + dynamicWriteAddressBook(1, schema); + } else if (strcmp(argv[1], "dread") == 0) { + StructSchema schema = Schema::from<AddressBook>(); + 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 000000000..33f78d5a5 --- /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 000000000..5d8452921 --- /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 <capnp/ez-rpc.h> +#include <kj/debug.h> +#include <math.h> +#include <iostream> + +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<void> 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<Calculator>(); + + // 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<PowerFunction>()); + 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 000000000..c2593be3a --- /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 <kj/debug.h> +#include <capnp/ez-rpc.h> +#include <capnp/message.h> +#include <iostream> + +typedef unsigned int uint; + +kj::Promise<double> 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<Calculator::Value::ReadResults> result) { + return result.getValue(); + }); +} + +kj::Promise<double> evaluateImpl( + Calculator::Expression::Reader expression, + capnp::List<double>::Reader params = capnp::List<double>::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<kj::Promise<double>> paramPromises = + KJ_MAP(param, call.getParams()) { + return evaluateImpl(param, params); + }; + + // Join the array of promises into a promise for an array. + kj::Promise<kj::Array<double>> joinedParams = + kj::joinPromises(kj::mv(paramPromises)); + + // When the parameters are complete, call the function. + return joinedParams.then([KJ_CPCAP(func)](kj::Array<double>&& paramValues) mutable { + auto request = func.callRequest(); + request.setParams(paramValues); + return request.send().then( + [](capnp::Response<Calculator::Function::CallResults>&& 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<void> 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<void> call(CallContext context) { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == paramCount, "Wrong number of parameters."); + + return evaluateImpl(body.getRoot<Calculator::Expression>(), 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<void> 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<void> evaluate(EvaluateContext context) override { + return evaluateImpl(context.getParams().getExpression()) + .then([KJ_CPCAP(context)](double value) mutable { + context.getResults().setValue(kj::heap<ValueImpl>(value)); + }); + } + + kj::Promise<void> defFunction(DefFunctionContext context) override { + auto params = context.getParams(); + context.getResults().setFunc(kj::heap<FunctionImpl>( + params.getParamCount(), params.getBody())); + return kj::READY_NOW; + } + + kj::Promise<void> getOperator(GetOperatorContext context) override { + context.getResults().setFunc(kj::heap<OperatorImpl>( + 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<CalculatorImpl>(), 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 000000000..adc8294e5 --- /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 000000000..862a237c6 --- /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/examples.qbs b/examples/examples.qbs index 2b1c0933b..18205d67a 100644 --- a/examples/examples.qbs +++ b/examples/examples.qbs @@ -68,5 +68,6 @@ Project { // "protobuf/addressbook_objc/addressbook_objc.qbs", "baremetal/baremetal.qbs", "rule/rule.qbs", + "rpaths/rpaths.qbs", ] } diff --git a/examples/rpaths/main.cpp b/examples/rpaths/main.cpp new file mode 100644 index 000000000..aaaebfbf6 --- /dev/null +++ b/examples/rpaths/main.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "objecta.h" +#include "objectb.h" + +#include <iostream> + +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 000000000..bf6266653 --- /dev/null +++ b/examples/rpaths/objecta.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#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 000000000..69b78fbc2 --- /dev/null +++ b/examples/rpaths/objecta.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECTA_H +#define OBJECTA_H + +#include <string> + +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 000000000..758fe09cf --- /dev/null +++ b/examples/rpaths/objectb.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#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 000000000..5a63de5eb --- /dev/null +++ b/examples/rpaths/objectb.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECTB_H +#define OBJECTB_H + +#include <string> + +#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 000000000..1e5411468 --- /dev/null +++ b/examples/rpaths/rpaths.qbs @@ -0,0 +1,58 @@ +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" + 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.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" + install: true + installDir: "examples/bin" + } + //! [2] +} diff --git a/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs b/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs index e0fe73b40..c3d98a49f 100644 --- a/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs +++ b/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs @@ -35,10 +35,5 @@ PathProbe { "/Library/Frameworks", "/System/Library/Frameworks" ]) - - nameFilter: { - return function(name) { - return name + ".framework"; - } - } + nameSuffixes: ".framework" } diff --git a/share/qbs/imports/qbs/Probes/PathProbe.qbs b/share/qbs/imports/qbs/Probes/PathProbe.qbs index 424a621c6..768defd87 100644 --- a/share/qbs/imports/qbs/Probes/PathProbe.qbs +++ b/share/qbs/imports/qbs/Probes/PathProbe.qbs @@ -38,11 +38,9 @@ Probe { property var nameFilter property var candidateFilter property varList selectors - property pathList pathPrefixes property pathList searchPaths property stringList pathSuffixes property pathList platformSearchPaths: hostOS.contains("unix") ? ['/usr', '/usr/local'] : [] - property pathList platformPaths property stringList environmentPaths property stringList platformEnvironmentPaths property stringList hostOS: qbs.hostOS @@ -57,15 +55,9 @@ Probe { property varList allResults configure: { - if (pathPrefixes) - console.warn("PathProbe.pathPrefixes is deprecated, use searchPaths instead"); - if (platformPaths) - console.warn("PathProbe.platformPaths is deprecated, use platformSearchPaths instead"); - var _searchPaths = ModUtils.concatAll(pathPrefixes, searchPaths); - var _platformSearchPaths = ModUtils.concatAll(platformPaths, platformSearchPaths); var results = PathProbeConfigure.configure(selectors, names, nameSuffixes, nameFilter, - candidateFilter, _searchPaths, pathSuffixes, - _platformSearchPaths, environmentPaths, + candidateFilter, searchPaths, pathSuffixes, + platformSearchPaths, environmentPaths, platformEnvironmentPaths, pathListSeparator); found = results.found; allResults = results.files; diff --git a/share/qbs/imports/qbs/Probes/path-probe.js b/share/qbs/imports/qbs/Probes/path-probe.js index a997f77f2..b4d745428 100644 --- a/share/qbs/imports/qbs/Probes/path-probe.js +++ b/share/qbs/imports/qbs/Probes/path-probe.js @@ -41,7 +41,7 @@ function asStringList(key, value) { throw key + " must be a string or a stringList"; } -function canonicalSelectors(selectors) { +function canonicalSelectors(selectors, nameSuffixes) { var mapper = function(selector) { if (typeof(selector) === "string") return {names : [selector]}; @@ -53,6 +53,8 @@ function canonicalSelectors(selectors) { 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); @@ -70,7 +72,7 @@ function configure(selectors, names, nameSuffixes, nameFilter, candidateFilter, {names: names, nameSuffixes: nameSuffixes} ]; } else { - selectors = canonicalSelectors(selectors); + selectors = canonicalSelectors(selectors, nameSuffixes); } if (nameFilter) { diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js index f4ec55328..c7b722563 100644 --- a/share/qbs/module-providers/Qt/setup-qt.js +++ b/share/qbs/module-providers/Qt/setup-qt.js @@ -107,7 +107,7 @@ function readFileContent(filePath) { // 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}=(.*)"); + 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); diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs index 2835a9336..410b05ff9 100644 --- a/share/qbs/module-providers/Qt/templates/android_support.qbs +++ b/share/qbs/module-providers/Qt/templates/android_support.qbs @@ -102,9 +102,8 @@ Module { targetArchitecture = theBinary.Android.ndk.abi; continue; } - if (theBinary.product.name === product.name - && candidate.product.name !== product.name) { - continue; // We already have a better match. + 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) { diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs index f285c6e61..bf2555fa3 100644 --- a/share/qbs/modules/bundle/BundleModule.qbs +++ b/share/qbs/modules/bundle/BundleModule.qbs @@ -52,6 +52,7 @@ Module { 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 @@ -83,8 +84,7 @@ Module { var specsPath = path; var specsSeparator = "-"; if (xcodeDeveloperPath && useXcodeBuildSpecs) { - specsPath = xcodeDeveloperPath - + "/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications"; + specsPath = Bundle.macOSSpecsPath(xcodeVersion, xcodeDeveloperPath); specsSeparator = " "; } diff --git a/share/qbs/modules/bundle/bundle.js b/share/qbs/modules/bundle/bundle.js index 2354d88b5..6d9305702 100644 --- a/share/qbs/modules/bundle/bundle.js +++ b/share/qbs/modules/bundle/bundle.js @@ -28,9 +28,11 @@ ** ****************************************************************************/ +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"); @@ -147,6 +149,17 @@ function _assign(target, source) { } } +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; diff --git a/share/qbs/modules/capnproto/capnproto.js b/share/qbs/modules/capnproto/capnproto.js new file mode 100644 index 000000000..dff379321 --- /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 000000000..e557f7b77 --- /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 000000000..e8c61dc89 --- /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/cpp/iar.js b/share/qbs/modules/cpp/iar.js index 3549979be..ffc0773e0 100644 --- a/share/qbs/modules/cpp/iar.js +++ b/share/qbs/modules/cpp/iar.js @@ -723,8 +723,7 @@ function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { // Misc flags. args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), - ModUtils.moduleProperty(input, "flags", tag), - ModUtils.moduleProperty(input, "driverFlags", tag)); + ModUtils.moduleProperty(input, "flags", tag)); return args; } diff --git a/share/qbs/modules/cpp/keil.js b/share/qbs/modules/cpp/keil.js index 441b49213..4cb2946a6 100644 --- a/share/qbs/modules/cpp/keil.js +++ b/share/qbs/modules/cpp/keil.js @@ -1011,8 +1011,7 @@ function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { // Misc flags. args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), - ModUtils.moduleProperty(input, "flags", tag), - ModUtils.moduleProperty(input, "driverFlags", tag)); + ModUtils.moduleProperty(input, "flags", tag)); return args; } diff --git a/share/qbs/modules/cpp/sdcc.js b/share/qbs/modules/cpp/sdcc.js index 9c7001601..2b544d27c 100644 --- a/share/qbs/modules/cpp/sdcc.js +++ b/share/qbs/modules/cpp/sdcc.js @@ -447,8 +447,7 @@ function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { // Misc flags. args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), - ModUtils.moduleProperty(input, "flags", tag), - ModUtils.moduleProperty(input, "driverFlags", tag)); + ModUtils.moduleProperty(input, "flags", tag)); args.push("-ol"); args.push(outputs.obj[0].filePath); diff --git a/share/qbs/modules/cpp/sdcc.qbs b/share/qbs/modules/cpp/sdcc.qbs index 1dec2555d..a8d0df3c9 100644 --- a/share/qbs/modules/cpp/sdcc.qbs +++ b/share/qbs/modules/cpp/sdcc.qbs @@ -105,14 +105,7 @@ CppModule { } FileTagger { - condition: qbs.architecture === "stm8"; - patterns: "*.s" - fileTags: ["asm"] - } - - FileTagger { - condition: qbs.architecture === "mcs51"; - patterns: ["*.s51", "*.asm"] + patterns: ["*.s", "*.a51", "*.asm"] fileTags: ["asm"] } diff --git a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs index 2697eb227..bd3d94929 100644 --- a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs +++ b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs @@ -54,14 +54,15 @@ ProtobufBase { inputs: ["protobuf.input", "protobuf.grpc"] outputFileTags: ["hpp", "cpp"] outputArtifacts: { + var outputDir = HelperFunctions.getOutputDir(input.protobuf.cpp, input); var result = [ - HelperFunctions.cppArtifact(input.protobuf.cpp, input, "hpp", ".pb.h"), - HelperFunctions.cppArtifact(input.protobuf.cpp, input, "cpp", ".pb.cc") + HelperFunctions.cppArtifact(outputDir, input, "hpp", ".pb.h"), + HelperFunctions.cppArtifact(outputDir, input, "cpp", ".pb.cc") ]; if (input.fileTags.contains("protobuf.grpc")) { result.push( - HelperFunctions.cppArtifact(input.protobuf.cpp, input, "hpp", ".grpc.pb.h"), - HelperFunctions.cppArtifact(input.protobuf.cpp, input, "cpp", ".grpc.pb.cc")); + HelperFunctions.cppArtifact(outputDir, input, "hpp", ".grpc.pb.h"), + HelperFunctions.cppArtifact(outputDir, input, "cpp", ".grpc.pb.cc")); } return result; diff --git a/share/qbs/modules/protobuf/objc/protobufobjc.qbs b/share/qbs/modules/protobuf/objc/protobufobjc.qbs index 67057fb26..9a910bb72 100644 --- a/share/qbs/modules/protobuf/objc/protobufobjc.qbs +++ b/share/qbs/modules/protobuf/objc/protobufobjc.qbs @@ -23,9 +23,10 @@ ProtobufBase { inputs: ["protobuf.input"] outputFileTags: ["hpp", "objc"] outputArtifacts: { + var outputDir = HelperFunctions.getOutputDir(input.protobuf.objc, input); return [ - HelperFunctions.objcArtifact(input.protobuf.objc, input, "hpp", ".pbobjc.h"), - HelperFunctions.objcArtifact(input.protobuf.objc, input, "objc", ".pbobjc.m") + HelperFunctions.objcArtifact(outputDir, input, "hpp", ".pbobjc.h"), + HelperFunctions.objcArtifact(outputDir, input, "objc", ".pbobjc.m") ]; } diff --git a/share/qbs/modules/protobuf/protobuf.js b/share/qbs/modules/protobuf/protobuf.js index 5fd7f0202..8ab535abd 100644 --- a/share/qbs/modules/protobuf/protobuf.js +++ b/share/qbs/modules/protobuf/protobuf.js @@ -69,11 +69,10 @@ function getOutputDir(module, input) { return outputDir; } -function cppArtifact(module, input, tag, suffix) { - var outputDir = getOutputDir(module, input); +function cppArtifact(outputDir, input, tag, suffix) { return { fileTags: [tag], - filePath: outputDir + "/" + FileInfo.baseName(input.fileName) + suffix, + filePath: FileInfo.joinPaths(outputDir, FileInfo.baseName(input.fileName) + suffix), cpp: { includePaths: [].concat(input.cpp.includePaths, outputDir), warningLevel: "none", @@ -81,11 +80,11 @@ function cppArtifact(module, input, tag, suffix) { }; } -function objcArtifact(module, input, tag, suffix) { - var outputDir = getOutputDir(module, input); +function objcArtifact(outputDir, input, tag, suffix) { return { fileTags: [tag], - filePath: outputDir + "/" + toCamelCase(FileInfo.baseName(input.fileName)) + suffix, + filePath: FileInfo.joinPaths( + outputDir, toCamelCase(FileInfo.baseName(input.fileName)) + suffix), cpp: { includePaths: [].concat(input.cpp.includePaths, outputDir), warningLevel: "none", diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js index 48cf3d772..9c87e09dc 100644 --- a/share/qbs/modules/xcode/xcode.js +++ b/share/qbs/modules/xcode/xcode.js @@ -183,6 +183,10 @@ function provisioningProfilePlistContents(filePath) { 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"; diff --git a/src/app/config-ui/config-ui.qbs b/src/app/config-ui/config-ui.qbs index 2222e6a17..8b5e37559 100644 --- a/src/app/config-ui/config-ui.qbs +++ b/src/app/config-ui/config-ui.qbs @@ -24,6 +24,7 @@ QbsApp { Properties { condition: qbs.targetOS.contains("macos") cpp.frameworks: ["ApplicationServices", "Cocoa"] + bundle.isBundle: false } Properties { diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h index c5d8ef980..2d676a7f7 100644 --- a/src/lib/corelib/language/item.h +++ b/src/lib/corelib/language/item.h @@ -78,6 +78,7 @@ public: QualifiedId name; Item *item; bool isProduct; + bool requiredValue = true; // base value of the required prop bool required; QVariantMap parameters; VersionRange versionRange; diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index 935f18f9e..09bfb00e8 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -1489,6 +1489,13 @@ void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext) 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()) @@ -2665,8 +2672,10 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare return; } + const bool isRequiredValue = + m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty()); const bool isRequired = !productTypesIsSet - && m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty()) + && isRequiredValue && !contains(m_requiredChain, false); const Version minVersion = Version::fromString( m_evaluator->stringValue(dependsItem, @@ -2686,8 +2695,8 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare const auto it = std::find_if(moduleResults->begin(), moduleResults->end(), [moduleName](const Item::Module &m) { return m.name == moduleName; }); if (it != moduleResults->end()) { - if (isRequired) - it->required = true; + it->required = it->required || isRequired; + it->requiredValue = it->requiredValue || isRequiredValue; it->versionRange.narrowDown(versionRange); continue; } @@ -2720,6 +2729,7 @@ void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *pare qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString(); result.name = moduleName; result.item = moduleItem; + result.requiredValue = isRequiredValue; result.required = isRequired; result.parameters = defaultParameters; result.versionRange = versionRange; diff --git a/src/plugins/generator/visualstudio/visualstudio.pro b/src/plugins/generator/visualstudio/visualstudio.pro index 5231c01dc..49aee0eb4 100644 --- a/src/plugins/generator/visualstudio/visualstudio.pro +++ b/src/plugins/generator/visualstudio/visualstudio.pro @@ -2,6 +2,8 @@ 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 diff --git a/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs b/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs index b262290bb..1183c9036 100644 --- a/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs +++ b/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs @@ -1,14 +1,24 @@ CppApplication { 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("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 { diff --git a/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs b/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs index b15f2ce9a..6f985c84b 100644 --- a/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs +++ b/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs @@ -1,14 +1,24 @@ StaticLibrary { 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("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 { 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 000000000..c7b894230 --- /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 000000000..0a13a5dc2 --- /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 000000000..f3fcd50fe --- /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 000000000..4ba005a47 --- /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 000000000..49e9d476e --- /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/c166-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/c166-keil.s new file mode 100644 index 000000000..394bc2ae4 --- /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/mcs251-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs251-keil.s new file mode 100644 index 000000000..312cc9680 --- /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-sdcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-sdcc.s new file mode 100644 index 000000000..eaa6467e3 --- /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 000000000..8e8a24980 --- /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 000000000..fbabe3ba8 --- /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 index e98279d6c..f556fc093 100644 --- 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 @@ -3,15 +3,61 @@ 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 === "msp430") + 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 === "msp430") + return true; + if (qbs.architecture === "xtensa") + return true; } console.info("unsupported toolset: %%" + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); return false; } - files: [qbs.architecture + "-" + qbs.toolchainType + ".s"] + + 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/stm8-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-iar.s new file mode 100644 index 000000000..674e20de6 --- /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 000000000..1a552f4a4 --- /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/xtensa-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/xtensa-gcc.s new file mode 100644 index 000000000..c21000905 --- /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/preinclude-headers.qbs b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs index 8ea1c3652..0ded6ff15 100644 --- a/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs +++ b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs @@ -1,6 +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/capnproto/bar.capnp b/tests/auto/blackbox/testdata/capnproto/bar.capnp new file mode 100644 index 000000000..a0e8a0f8c --- /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 000000000..8b2fe4faf --- /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 000000000..0e8979eec --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.cpp @@ -0,0 +1,14 @@ +#include "baz.capnp.h" + +#include <capnp/message.h> + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto baz = message.initRoot<Baz>(); + 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 000000000..ee0903f73 --- /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 000000000..b9f729955 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.cpp @@ -0,0 +1,13 @@ +#include "foo.capnp.h" + +#include <capnp/message.h> + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto foo = message.initRoot<Foo>(); + 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 000000000..d7ee1b4c9 --- /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 000000000..5116bd3d6 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.cpp @@ -0,0 +1,14 @@ +#include "bar.capnp.h" + +#include <capnp/message.h> + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto bar = message.initRoot<Bar>(); + 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 000000000..7c1991d8f --- /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 000000000..146a2969f --- /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 000000000..d3fcdb4e3 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter-client.cpp @@ -0,0 +1,25 @@ +#include "greeter.capnp.h" + +#include <capnp/ez-rpc.h> + +#include <iostream> + +int main(int argc, char *argv[]) +{ + const char address[] = "localhost:5050"; + capnp::EzRpcClient client(address); + Greeter::Client greeter = client.getMain<Greeter>(); + + 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 000000000..a7f482cc8 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter-server.cpp @@ -0,0 +1,27 @@ +#include "greeter.capnp.h" + +#include <capnp/ez-rpc.h> +#include <capnp/message.h> + +#include <iostream> + +class GreeterImpl final: public Greeter::Server +{ +public: + ::kj::Promise<void> 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<GreeterImpl>(), 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 000000000..b9188f634 --- /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 000000000..cf95b968b --- /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 000000000..146a2969f --- /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/path-probe/mult-files-common-suffixes.qbs b/tests/auto/blackbox/testdata/path-probe/mult-files-common-suffixes.qbs new file mode 100644 index 000000000..c4d53a715 --- /dev/null +++ b/tests/auto/blackbox/testdata/path-probe/mult-files-common-suffixes.qbs @@ -0,0 +1,10 @@ +BaseApp { + inputSelectors: [ + {names : "tool"}, + {names : "super-tool"}, + ] + inputNameSuffixes: ".1" + inputSearchPaths: "bin" + outputFilePaths: ["bin/tool.1", "bin/super-tool.1"] + outputCandidatePaths: [["bin/tool.1"], ["bin/super-tool.1"]] +} 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 000000000..fd52488fb --- /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 000000000..605a2aaee --- /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 000000000..ac7dbbec6 --- /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 000000000..0bdd8c3b7 --- /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 000000000..209b1e47d --- /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/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index c6e8b9731..66f195ff0 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -739,6 +739,32 @@ void TestBlackbox::buildVariantDefaults() 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<QString>("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<bool>("useChangedFilesForInitialBuild"); @@ -2935,6 +2961,7 @@ void TestBlackbox::pathProbe_data() 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; @@ -8111,6 +8138,16 @@ void TestBlackbox::qbsVersion() 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"); diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index de357169d..e958a113c 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -62,6 +62,8 @@ private slots: void buildGraphVersions(); void buildVariantDefaults_data(); void buildVariantDefaults(); + void capnproto(); + void capnproto_data(); void changedFiles_data(); void changedFiles(); void changedInputsFromDependencies(); @@ -313,6 +315,7 @@ private slots: void trackRemoveFile(); void trackRemoveFileTag(); void trackRemoveProduct(); + void transitiveInvalidDependencies(); void transitiveOptionalDependencies(); void typescript(); void undefinedTargetPlatform(); diff --git a/tests/auto/blackbox/tst_blackboxbaremetal.cpp b/tests/auto/blackbox/tst_blackboxbaremetal.cpp index a80a1ad8f..2f6169ff9 100644 --- a/tests/auto/blackbox/tst_blackboxbaremetal.cpp +++ b/tests/auto/blackbox/tst_blackboxbaremetal.cpp @@ -110,7 +110,16 @@ void TestBlackboxBareMetal::distributionIncludePaths() void TestBlackboxBareMetal::preincludeHeaders() { QDir::setCurrent(testDataDir + "/preinclude-headers"); - QCOMPARE(runQbs(), 0); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (!m_qbsStdout.contains("unsupported toolset:")) { + QCOMPARE(runQbs(), 0); + } else { + QByteArray toolchain; + QByteArray architecture; + extractUnsupportedToolset(m_qbsStdout, toolchain, architecture); + QSKIP("Unsupported toolchain '" + toolchain + + "' for architecture '" + architecture + "'"); + } } QTEST_MAIN(TestBlackboxBareMetal) diff --git a/tests/auto/language/language.pro b/tests/auto/language/language.pro index 86248d926..cdb067195 100644 --- a/tests/auto/language/language.pro +++ b/tests/auto/language/language.pro @@ -20,6 +20,5 @@ for(data_dir, DATA_DIRS) { OTHER_FILES += $$FILES qbs_use_bundled_qtscript { - CONFIG += qbs_do_not_link_bundled_qtscript include(../../../src/lib/scriptengine/use_scriptengine.pri) } |