diff --git a/.ci b/.ci index ad8dd4a..93062ba 160000 --- a/.ci +++ b/.ci @@ -1 +1 @@ -Subproject commit ad8dd4a136348c7dc889fdc03a645e0f0358a2dc +Subproject commit 93062ba941879f5eeaef40308b406c9d5dbf51c5 diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 7ae898d..7c65004 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -3,7 +3,7 @@ # This is YAML - indentation levels are crucial -# Set the 'name:' properties to values that work for you (pvxs) +# Workflow name, shared by all branches name: pvData @@ -11,143 +11,289 @@ name: pvData on: push: paths-ignore: - - .appveyor.yml + - 'documentation/*' + - 'startup/*' + - '.appveyor/*' + - '.tools/*' + - '.gitattributes' + - '**/*.html' + - '**/*.md' pull_request: + paths-ignore: + - 'documentation/*' + - 'startup/*' + - '.appveyor/*' + - '.tools/*' + - '.gitattributes' + - '**/*.html' + - '**/*.md' env: SETUP_PATH: .ci-local:.ci EPICS_TEST_IMPRECISE_TIMING: YES + EPICS_TEST_TIMEOUT: 300 # 5 min (RTEMS epicsMessageQueue is slowest) jobs: - build-base: - name: ${{ matrix.base }}/${{ matrix.os }}/${{ matrix.cmp }}/${{ matrix.configuration }}/${{ matrix.wine }}${{ matrix.rtems }}/${{ matrix.extra }} + native: + name: ${{ matrix.name }} runs-on: ${{ matrix.os }} # Set environment variables from matrix parameters env: - BASE: ${{ matrix.base }} CMP: ${{ matrix.cmp }} BCFG: ${{ matrix.configuration }} + BASE: ${{ matrix.base }} WINE: ${{ matrix.wine }} RTEMS: ${{ matrix.rtems }} + RTEMS_TARGET: ${{ matrix.rtems_target }} EXTRA: ${{ matrix.extra }} TEST: ${{ matrix.test }} strategy: fail-fast: false matrix: + # Job names also name artifacts, character limitations apply include: - - os: ubuntu-20.04 + - os: ubuntu-latest cmp: gcc configuration: default base: "7.0" wine: "64" + name: "Ub-20 gcc-9 + MinGW" - - os: ubuntu-20.04 + - os: ubuntu-latest cmp: gcc configuration: static base: "7.0" wine: "64" + name: "Ub-20 gcc-9 + MinGW, static" - - os: ubuntu-20.04 - cmp: gcc - configuration: default - base: "3.15" - wine: "64" - - - os: ubuntu-20.04 + - os: ubuntu-latest cmp: gcc configuration: static base: "7.0" extra: "CMD_CXXFLAGS=-std=c++11" + name: "Ub-20 gcc-9 C++11, static" - - os: ubuntu-16.04 + - os: ubuntu-latest cmp: clang configuration: default base: "7.0" + name: "Ub-20 clang-10" - - os: ubuntu-20.04 + - os: ubuntu-latest cmp: clang configuration: default base: "7.0" extra: "CMD_CXXFLAGS=-std=c++11" + name: "Ub-20 clang-10 C++11" + + - os: ubuntu-20.04 + cmp: gcc + configuration: default + base: "7.0" + rtems: "5" + rtems_target: RTEMS-pc686-qemu + name: "Ub-20 gcc-9 + RT-5.1 pc686" + + - os: ubuntu-20.04 + cmp: gcc + configuration: default + base: "7.0" + rtems: "5" + rtems_target: RTEMS-beatnik + test: NO + name: "Ub-20 gcc-9 + RT-5.1 beatnik" + + - os: ubuntu-20.04 + cmp: gcc + configuration: default + base: "7.0" + rtems: "5" + rtems_target: RTEMS-xilinx_zynq_a9_qemu + test: NO + name: "Ub-20 gcc-9 + RT-5.1 xilinx_zynq_a9_qemu" + + - os: ubuntu-20.04 + cmp: gcc + configuration: default + base: "7.0" + rtems: "5" + rtems_target: RTEMS-uC5282 + test: NO + name: "Ub-20 gcc-9 + RT-5.1 uC5282" - os: ubuntu-20.04 cmp: gcc configuration: default base: "7.0" rtems: "4.10" + name: "Ub-20 gcc-9 + RT-4.10" + rtems_target: RTEMS-pc386-qemu + test: NO - os: ubuntu-20.04 cmp: gcc configuration: default base: "7.0" rtems: "4.9" - - - os: ubuntu-16.04 - cmp: gcc-4.8 - utoolchain: true - configuration: default - base: "7.0" - - - os: ubuntu-16.04 - cmp: gcc-4.9 - utoolchain: true - configuration: default - base: "7.0" + name: "Ub-20 gcc-9 + RT-4.9" + rtems_target: RTEMS-pc386-qemu - os: ubuntu-20.04 - cmp: gcc-8 - utoolchain: true + cmp: gcc configuration: default - base: "7.0" - - - os: ubuntu-20.04 - cmp: clang - configuration: default - base: "7.0" + base: "3.15" + name: "Ub-20 3.15" - os: macos-latest cmp: clang configuration: default base: "7.0" + name: "MacOS clang-12" - os: windows-2019 cmp: vs2019 configuration: default base: "7.0" + name: "Win2019 MSC-19" - os: windows-2019 cmp: vs2019 configuration: static base: "7.0" + name: "Win2019 MSC-19, static" + + - os: windows-2019 + cmp: vs2019 + configuration: debug + base: "7.0" + name: "Win2019 MSC-19, debug" + + - os: windows-2019 + cmp: gcc + configuration: default + base: "7.0" + name: "Win2019 mingw" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - - name: Cache Dependencies - uses: actions/cache@v2 - with: - path: ~/.cache - key: ${{ matrix.base }}/${{ matrix.os }}/${{ matrix.cmp }}/${{ matrix.configuration }}/${{ matrix.wine }}${{ matrix.rtems }}/${{ matrix.extra }} - - name: Automatic core dump analysis + - name: Automatic core dumper analysis uses: mdavidsaver/ci-core-dumper@master - name: "apt-get install" run: | sudo apt-get update sudo apt-get -y install qemu-system-x86 g++-mingw-w64-x86-64 gdb if: runner.os == 'Linux' - - name: "apt-get install ${{ matrix.cmp }}" - run: | - sudo apt-get -y install software-properties-common - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get -y install ${{ matrix.cmp }} - if: matrix.utoolchain - name: Prepare and compile dependencies run: python .ci/cue.py prepare - name: Build main module run: python .ci/cue.py build - name: Run main module tests - run: python .ci/cue.py test + run: python .ci/cue.py -T 60M test + - name: Upload tapfiles Artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: tapfiles ${{ matrix.name }} + path: '**/O.*/*.tap' + if-no-files-found: ignore - name: Collect and show test results - run: python .ci/cue.py test-results + if: ${{ always() }} + run: python .ci/cue.py -T 5M test-results + + docker: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + # Set environment variables from matrix parameters + env: + CMP: ${{ matrix.cmp }} + BCFG: ${{ matrix.configuration }} + BASE: ${{ matrix.base }} + EXTRA: ${{ matrix.extra }} + TEST: ${{ matrix.test }} + strategy: + fail-fast: false + matrix: + # Job names also name artifacts, character limitations apply + include: + - name: "CentOS-7 3.16" + image: centos:7 + cmp: gcc + configuration: default + base: "3.16" + + - name: "CentOS-7 3.15" + image: centos:7 + cmp: gcc + configuration: default + base: "3.15" + + - name: "CentOS-7" + image: centos:7 + cmp: gcc + configuration: default + base: "7.0" + + - name: "Fedora-33" + image: fedora:33 + cmp: gcc + configuration: default + base: "7.0" + + - name: "Fedora-latest" + image: fedora:latest + cmp: gcc + configuration: default + base: "7.0" + + steps: + - name: "Build newer Git" + # actions/checkout@v2 wants git >=2.18 + # centos:7 has 1.8 + if: matrix.image=='centos:7' + run: | + yum -y install curl make gcc curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker + curl https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz | tar -xz + cd git-* + make -j2 prefix=/usr/local all + make prefix=/usr/local install + cd .. + rm -rf git-* + type -a git + git --version + - name: "Redhat setup" + run: | + dnfyum() { + dnf -y "$@" || yum -y "$@" + return $? + } + dnfyum install python3 gdb make perl gcc-c++ glibc-devel readline-devel ncurses-devel perl-devel perl-Test-Simple + git --version || dnfyum install git + # rather than just bite the bullet and link python3 -> python, + # people would rather just break all existing scripts... + [ -e /usr/bin/python ] || ln -sf python3 /usr/bin/python + python --version + - uses: actions/checkout@v3 + with: + submodules: true + - name: Automatic core dumper analysis + uses: mdavidsaver/ci-core-dumper@master + - name: Prepare and compile dependencies + run: python .ci/cue.py prepare + - name: Build main module + run: python .ci/cue.py build + - name: Run main module tests + run: python .ci/cue.py -T 20M test + - name: Upload tapfiles Artifact + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: tapfiles ${{ matrix.name }} + path: '**/O.*/*.tap' + if-no-files-found: ignore + - name: Collect and show test results + if: ${{ always() }} + run: python .ci/cue.py -T 5M test-results diff --git a/Doxyfile b/Doxyfile index 790af80..5e8247a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "PVData C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 8.0.4 +PROJECT_NUMBER = 8.0.6-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/configure/CONFIG_PVDATA_VERSION b/configure/CONFIG_PVDATA_VERSION index 1d30854..1def5e6 100644 --- a/configure/CONFIG_PVDATA_VERSION +++ b/configure/CONFIG_PVDATA_VERSION @@ -2,11 +2,11 @@ EPICS_PVD_MAJOR_VERSION = 8 EPICS_PVD_MINOR_VERSION = 0 -EPICS_PVD_MAINTENANCE_VERSION = 4 +EPICS_PVD_MAINTENANCE_VERSION = 6 # Development flag, set to zero for release versions -EPICS_PVD_DEVELOPMENT_FLAG = 0 +EPICS_PVD_DEVELOPMENT_FLAG = 1 # Immediately after a release the MAINTENANCE_VERSION # will be incremented and the DEVELOPMENT_FLAG set to 1 diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 0cc83b6..56a1be5 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "PVData C++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 8.0.4 +PROJECT_NUMBER = 8.0.6-dev # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/documentation/release_notes.dox b/documentation/release_notes.dox index 5bb895e..27bc1aa 100644 --- a/documentation/release_notes.dox +++ b/documentation/release_notes.dox @@ -2,7 +2,17 @@ @page release_notes Release Notes -Release 8.1.0 (Feb 2021) +Release 8.0.6 (UNRELEASED) +======================== + + +Release 8.0.5 (Sep 2022) +======================== + +- Compatible changes + - Internal changes to use the YAJL API for generating JSON and JSON-5 output. + +Release 8.0.4 (Feb 2021) ======================== - Incompatible changes diff --git a/src/factory/printer.cpp b/src/factory/printer.cpp index 928fc4f..55fb01c 100644 --- a/src/factory/printer.cpp +++ b/src/factory/printer.cpp @@ -497,7 +497,7 @@ std::ostream& operator<<(std::ostream& strm, const escape& Q) case '\'': next = '\''; break; case '\"': next = '\"'; if(Q.S==escape::CSV) quote = '"'; break; default: - if(!isprint(C)) { + if(!isprint((unsigned char)C)) { // print three charator escape strm<<"\\x"<>4)< #include +#include +#include + #define epicsExportSharedSymbols #include #include @@ -16,28 +19,94 @@ namespace pvd = epics::pvData; namespace { +using namespace pvd::yajl; + +void yg(yajl_gen_status sts) { + const char *msg = "<\?\?\?>"; + switch(sts) { + case yajl_gen_status_ok: + case yajl_gen_generation_complete: + return; +#define CASE(STS) case STS: msg = #STS; break + CASE(yajl_gen_keys_must_be_strings); + CASE(yajl_gen_in_error_state); + CASE(yajl_gen_no_buf); + CASE(yajl_gen_invalid_number); + CASE(yajl_max_depth_exceeded); +#ifdef EPICS_YAJL_VERSION + CASE(yajl_gen_invalid_string); +#endif +#undef CASE + } + throw std::runtime_error(msg); +} + +static +void stream_printer(void * ctx, + const char * str, + size_arg len) +{ + std::ostream *strm = (std::ostream*)ctx; + strm->write(str, len); +} struct args { - std::ostream& strm; + yajl_gen handle; const pvd::JSONPrintOptions& opts; - unsigned indent; + std::string indent; args(std::ostream& strm, const pvd::JSONPrintOptions& opts) - :strm(strm) - ,opts(opts) - ,indent(opts.indent) - {} + :opts(opts) + ,indent(opts.indent, ' ') + { +#ifndef EPICS_YAJL_VERSION + yajl_gen_config conf; + conf.beautify = opts.multiLine; + conf.indentString = indent.c_str(); + if(!(handle = yajl_gen_alloc2(stream_printer, NULL, NULL, &strm))) + throw std::bad_alloc(); - void doIntent() { - if(!opts.multiLine) return; - strm.put('\n'); - unsigned i=indent; - while(i--) strm.put(' '); + if(opts.json5) { + static bool warned; + if(!warned) { + warned = true; + errlogPrintf("Warning: Ignoring request to print JSON5. Update Base >= 7.0.6.1"); + } + } +#else + if(!(handle = yajl_gen_alloc(NULL))) + throw std::bad_alloc(); + if(opts.multiLine) { + yajl_gen_config(handle, yajl_gen_beautify, 1); + yajl_gen_config(handle, yajl_gen_indent_string, indent.c_str()); + } else { + yajl_gen_config(handle, yajl_gen_beautify, 0); + } +# if EPICS_VERSION_INT>=VERSION_INT(7,0,6,1) + yajl_gen_config(handle, yajl_gen_json5, (int)opts.json5); +# else + if(opts.json5) { + static bool warned; + if(!warned) { + warned = true; + errlogPrintf("Warning: Ignoring request to print JSON5. Update Base >= 7.0.6.1"); + } + } +# endif + yajl_gen_config(handle, yajl_gen_print_callback, stream_printer, &strm); +#endif + } + ~args() { + yajl_gen_free(handle); } }; +void yg_string(yajl_gen handle, const std::string& s) { + yg(yajl_gen_string(handle, (const unsigned char*)s.c_str(), s.size())); +} + void show_field(args& A, const pvd::PVField* fld, const pvd::BitSet *mask); void show_struct(args& A, const pvd::PVStructure* fld, const pvd::BitSet *mask) @@ -47,26 +116,17 @@ void show_struct(args& A, const pvd::PVStructure* fld, const pvd::BitSet *mask) const pvd::StringArray& names = type->getFieldNames(); - A.strm.put('{'); - A.indent++; + yg(yajl_gen_map_open(A.handle)); - bool first = true; for(size_t i=0, N=names.size(); iget(children[i]->getFieldOffset())) continue; - if(first) - first = false; - else - A.strm.put(','); - A.doIntent(); - A.strm<<'\"'<(fld); - if(scalar->getScalar()->getScalarType()==pvd::pvString) { - A.strm<<'\"'<getAs()<<'\"'; - } else { - A.strm<getAs(); + switch(scalar->getScalar()->getScalarType()) { + case pvd::pvString: yg_string(A.handle, scalar->getAs()); break; + case pvd::pvBoolean: yg(yajl_gen_bool(A.handle, scalar->getAs())); break; + case pvd::pvDouble: + case pvd::pvFloat: yg(yajl_gen_double(A.handle, scalar->getAs())); break; + // case pvd::pvULong: // can't always be exactly represented... + default: + yg(yajl_gen_integer(A.handle, scalar->getAs())); break; } } return; case pvd::scalarArray: { const pvd::PVScalarArray *scalar=static_cast(fld); - const bool isstring = scalar->getScalarArray()->getElementType()==pvd::pvString; pvd::shared_vector arr; scalar->getAs(arr); - pvd::shared_vector sarr(pvd::shared_vector_convert(arr)); + yg(yajl_gen_array_open(A.handle)); - A.strm.put('['); - for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i(fld)->view()); - A.strm.put('['); - A.indent++; + yg(yajl_gen_array_open(A.handle)); for(size_t i=0, N=arr.size(); iget()); if(!C) { - A.strm<<"null"; + yg(yajl_gen_null(A.handle)); } else { show_field(A, C.get(), 0); } @@ -145,29 +224,23 @@ void show_field(args& A, const pvd::PVField* fld, const pvd::BitSet *mask) case pvd::unionArray: { const pvd::PVUnionArray *U=static_cast(fld); pvd::PVUnionArray::const_svector arr(U->view()); - A.strm.put('['); - A.indent++; + + yg(yajl_gen_array_open(A.handle)); for(size_t i=0, N=arr.size(); i(val, "any", "4.2"); - testFieldEqual(val, "almost", "hello"); + testFieldEqual(val, "almost", "long string /with\\ several \" and ' characters\x1f\xe2\x9c\x85"); } void testroundtrip() @@ -255,19 +255,19 @@ void testroundtrip() round2 = strm.str(); } - testEqual(round2, "{\"scalar\": 42," - "\"ivec\": [1,2,3]," - "\"svec\": [\"one\",\"two\"]," - "\"sub\": {" - "\"x\": {" - "\"y\": 43" + testEqual(round2, "{\"scalar\":42," + "\"ivec\":[1,2,3]," + "\"svec\":[\"one\",\"two\"]," + "\"sub\":{" + "\"x\":{" + "\"y\":43" "}}," - "\"extra\": 0," - "\"sarr\": [{\"a\": 5,\"b\": 6}," - "{\"a\": 7,\"b\": 8}," - "{\"a\": 9,\"b\": 10}]," - "\"any\": \"4.2\"," - "\"almost\": \"hello\"" + "\"extra\":0," + "\"sarr\":[{\"a\":5,\"b\":6}," + "{\"a\":7,\"b\":8}," + "{\"a\":9,\"b\":10}]," + "\"any\":\"4.2\"," + "\"almost\":\"long string /with\\\\ several \\\" and ' characters\\u001F\xe2\x9c\x85\"" "}"); } diff --git a/testApp/misc/testprinter.cpp b/testApp/misc/testprinter.cpp index 417dd5e..767b917 100644 --- a/testApp/misc/testprinter.cpp +++ b/testApp/misc/testprinter.cpp @@ -247,7 +247,7 @@ void showNTTable() iarr.push_back(42); // will not be shown input->getSubFieldT("value.colA")->replace(pvd::freeze(iarr)); - sarr.push_back("one\x7f"); + sarr.push_back("one\x7f\x80"); sarr.push_back("two words"); sarr.push_back("A '\"'"); input->getSubFieldT("value.colB")->replace(pvd::freeze(sarr)); @@ -255,7 +255,7 @@ void showNTTable() testDiff(" \n" "labelA \"label B\"\n" - " 1 one\\x7F\n" + " 1 one\\x7F\\x80\n" " 2 \"two words\"\n" " 3 \"A \\'\"\"\\'\"\n" , print(input->stream()),