diff options
85 files changed, 3554 insertions, 1593 deletions
@@ -1,6 +1,4 @@ # generated files and folders -.waf-* -.lock-waf* /build *.pyc NEWS diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea14a6b..92ff3ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,92 +1,165 @@ -stages: [build, deploy] - -variables: - GIT_SUBMODULE_STRATEGY: normal - -.build_template: &build_definition - stage: build - artifacts: - paths: ["build/", ".lock-waf*"] - -.test_template: &test_definition - stage: test - artifacts: - paths: [build/coverage] - - arm32_dbg: - <<: *build_definition image: lv2plugin/debian-arm32 - script: python3 ./waf configure build test -dST --werror --wrapper=qemu-arm-static - variables: - CC: "arm-linux-gnueabihf-gcc" - CXX: "arm-linux-gnueabihf-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/arm-linux-gnueabihf.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test arm32_rel: - <<: *build_definition image: lv2plugin/debian-arm32 - script: python3 ./waf configure build test -ST --werror --wrapper=qemu-arm-static - variables: - CC: "arm-linux-gnueabihf-gcc" - CXX: "arm-linux-gnueabihf-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/arm-linux-gnueabihf.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test arm64_dbg: - <<: *build_definition image: lv2plugin/debian-arm64 - script: python3 ./waf configure build test -dST --werror --wrapper=qemu-aarch64-static - variables: - CC: "aarch64-linux-gnu-gcc" - CXX: "aarch64-linux-gnu-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/aarch64-linux-gnu.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test arm64_rel: - <<: *build_definition image: lv2plugin/debian-arm64 - script: python3 ./waf configure build test -ST --werror --wrapper=qemu-aarch64-static - variables: - CC: "aarch64-linux-gnu-gcc" - CXX: "aarch64-linux-gnu-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/aarch64-linux-gnu.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +x32_dbg: + image: lv2plugin/debian-x32 + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-linux-gnu.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + +x32_rel: + image: lv2plugin/debian-x32 + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-linux-gnu.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test x64_dbg: - <<: *build_definition image: lv2plugin/debian-x64 - script: python3 ./waf configure build test -dST --werror + script: + - meson setup build -Dbuildtype=debug -Dstrict=true -Dwerror=true -Db_coverage=true + - ninja -C build test + - ninja -C build coverage-html + coverage: '/ *lines\.*: \d+\.\d+.*/' + artifacts: + paths: + - build/meson-logs/coveragereport x64_rel: - <<: *build_definition image: lv2plugin/debian-x64 - script: python3 ./waf configure build test -ST --werror + script: + - meson setup build -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +x64_static: + image: lv2plugin/debian-x64 + script: + - meson setup build -Ddefault_library=static -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +x64_sanitize: + image: lv2plugin/debian-x64-clang + script: + - meson setup build -Db_lundef=false -Dbuildtype=plain -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + variables: + CC: "clang" + CFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" + LDFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" + + +freebsd_dbg: + tags: [freebsd,meson] + script: + - meson setup build -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + +freebsd_rel: + tags: [freebsd,meson] + script: + - meson setup build -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +mingw32_dbg: + image: lv2plugin/debian-mingw32 + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-w64-mingw32.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + +mingw32_rel: + image: lv2plugin/debian-mingw32 + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-w64-mingw32.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +mingw64_dbg: + image: lv2plugin/debian-mingw64 + script: + - meson setup build --cross-file=/usr/share/meson/cross/x86_64-w64-mingw32.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + +mingw64_rel: + image: lv2plugin/debian-mingw64 + script: + - meson setup build --cross-file=/usr/share/meson/cross/x86_64-w64-mingw32.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test mac_dbg: - <<: *build_definition - script: python3 ./waf configure build test -dST --werror --no-coverage tags: [macos] + script: + - meson setup build -Dbuildtype=debug -Dstrict=true -Dwerror=true + - ninja -C build test mac_rel: - <<: *build_definition - script: python3 ./waf configure build test -ST --werror --no-coverage tags: [macos] + script: + - meson setup build -Dbuildtype=release -Dstrict=true -Dwerror=true + - ninja -C build test win_dbg: - <<: *build_definition - script: python ./waf configure build test -dST --werror --no-coverage - tags: [windows,msvc,python] - + tags: [windows,meson] + script: + - meson setup build -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test win_rel: - <<: *build_definition - script: python ./waf configure build test -ST --werror --no-coverage - tags: [windows,msvc,python] + tags: [windows,meson] + script: + - meson setup build -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddocs=disabled + - ninja -C build test + + +wasm_dbg: + image: lv2plugin/debian-wasm + script: + - meson setup build --cross-file=/usr/share/meson/cross/wasm.ini -Dbuildtype=debug -Dstrict=true -Dwerror=true -Ddefault_library=static -Ddocs=disabled -Dplugins=disabled + - ninja -C build test + +wasm_rel: + image: lv2plugin/debian-wasm + script: + - meson setup build --cross-file=/usr/share/meson/cross/wasm.ini -Dbuildtype=release -Dstrict=true -Dwerror=true -Ddefault_library=static -Ddocs=disabled -Dplugins=disabled + - ninja -C build test pages: stage: deploy - script: mv build/coverage/ public/ - dependencies: ["x64_dbg"] + script: + - mkdir -p public + - mv build/meson-logs/coveragereport/ public/coverage + dependencies: + - x64_dbg artifacts: - paths: [public] + paths: + - public only: - master diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index cc8b569..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "waflib"] - path = waflib - url = ../../drobilla/autowaf.git diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..400391f --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,72 @@ +Installation Instructions +========================= + +Prerequisites +------------- + +To build from source, you will need: + + * A relatively modern C compiler (GCC, Clang, and MSVC are known to work). + + * [Meson](http://mesonbuild.com/), which depends on + [Python](http://python.org/). + +This is a brief overview of building this project with meson. See the meson +documentation for more detailed information. + +Configuration +------------- + +The build is configured with the `setup` command, which creates a new build +directory with the given name: + + meson setup build + +Some environment variables are read during `setup` and stored with the +configuration: + + * `CC`: Path to C compiler. + * `CFLAGS`: C compiler options. + * `CXX`: Path to C++ compiler. + * `CXXFLAGS`: C++ compiler options. + * `LDFLAGS`: Linker options. + +However, it is better to use meson options for configuration. All options can +be inspected with the `configure` command from within the build directory: + + cd build + meson configure + +Options can be set by passing C-style "define" options to `configure`: + + meson configure -Dc_args="-march=native" -Dprefix="/opt/mypackage/" + +Building +-------- + +From within a configured build directory, everything can be built with the +`compile` command: + + meson compile + +Similarly, tests can be run with the `test` command: + + meson test + +Meson can also generate a project for several popular IDEs, see the `backend` +option for details. + +Installation +------------ + +A compiled project can be installed with the `install` command: + + meson install + +You may need to acquire root permissions to install to a system-wide prefix. +For packaging, the installation may be staged to a directory using the +`DESTDIR` environment variable or the `--destdir` option: + + DESTDIR=/tmp/mypackage/ meson install + + meson install --destdir=/tmp/mypackage/ @@ -1,82 +1,49 @@ LV2 === -LV2 is a plugin standard for audio systems. It defines a minimal yet extensible -C API for plugin code and a format for plugin "bundles". See -<http://lv2plug.in> for more information. - -This package contains specifications (C headers and Turtle files), -documentation generation tools, and example plugins. - -Building and installation requires only Python 2.6. Building the documentation -requires Doxygen. +LV2 is a plugin standard for audio systems. It defines an extensible C API for +plugins, and a format for self-contained "bundle" directories that contain +plugins, metadata, and other resources. See <http://lv2plug.in/> for more +information. +This package contains specifications (C headers and Turtle data files), +documentation generation tools, tests, and example plugins. Installation ------------ -A typical build looks something like this: - - ./waf configure --prefix=/foo - ./waf - sudo ./waf install - -or, for packaging: - - DESTDIR=/home/packager/lv2root ./waf install - -For help on the other available options, run: - - ./waf --help - -The bundle installation directory can be set with the --lv2dir option, e.g.: - - ./waf configure --lv2dir=/foo/lib/lv2 - -Configuring with `--lv2-user` will install bundles to the user-local location. +See the [installation instructions](INSTALL.md) for details on how to +configure, build, and install LV2 with meson. +By default, everything is installed within the `prefix` with a UNIX-style +hierarchy, and LV2 bundles are installed in the "lv2" subdirectory of the +`libdir`. The bundle installation directory can be overridden with the +`lv2dir` option. For example, standard system-wide values for various +operating systems are: -Packaging ---------- + meson configure -Dlv2dir=/Library/Audio/Plug-Ins/LV2 + meson configure -Dlv2dir=/boot/common/add-ons/lv2 + meson configure -Dlv2dir=C:/Program Files/Common/LV2 -Specification bundles are both a build-time and run-time dependency of programs -that use LV2. Programs expect their data to be available somewhere in -`LV2_PATH`. +The [specification bundles](lv2) are run-time dependencies of LV2 applications. +Programs expect their data to be available somewhere in `LV2_PATH`. See +<http://lv2plug.in/pages/filesystem-hierarchy-standard.html> for details on the +standard installation paths. -See <http://lv2plug.in/pages/filesystem-hierarchy-standard.html> for details on -the standard installation paths. +Headers +------- -Do not split up LV2 bundles, they are self-contained and must remain whole. -Other than that, things may be split to suit distribution needs. For example, -separate packages for specifications, tools, and plugins, may be good idea. +The `lv2/` include namespace is reserved for this LV2 distribution. +Other projects may extend LV2, but must place their headers elsewhere. - -Header Installation -------------------- - -By default symbolic links to headers in bundles are installed to `INCLUDEDIR`. -If symbolic links are a problem, configure with `--copy-headers` and copies -will be installed instead. - -Headers are installed in two paths, the universal URI-based style: - - #include "lv2/lv2plug.in/ns/ext/urid/urid.h" - -and the newer simple core style: +Headers are installed to `includedir` with paths like: #include "lv2/urid/urid.h" -Projects are encouraged to migrate to the latter style, though note that this -style of include path may only be used by official LV2 specifications. - - -Documentation -------------- +For backwards compatibility, if the `old_headers` option is set, then headers +are also installed to the older URI-based paths: -Configuring with the --docs option will build the documentation for all the -included specifications if Doxygen is available. For example: - - ./waf configure --docs - ./waf + #include "lv2/lv2plug.in/ns/ext/urid/urid.h" -Specification documentation is also available online at <http://lv2plug.in/ns>. +Projects still using this style are encourated to migrate to the shorter style +above. diff --git a/doc/doxy-style.css b/doc/c/doxy-style.css index b44675e..b44675e 100644 --- a/doc/doxy-style.css +++ b/doc/c/doxy-style.css diff --git a/doc/footer.html b/doc/c/footer.html index 0dc6919..0dc6919 100644 --- a/doc/footer.html +++ b/doc/c/footer.html diff --git a/doc/header.html b/doc/c/header.html index 2e419e3..2e419e3 100644 --- a/doc/header.html +++ b/doc/c/header.html diff --git a/doc/layout.xml b/doc/c/layout.xml index 1f63a76..1f63a76 100644 --- a/doc/layout.xml +++ b/doc/c/layout.xml diff --git a/doc/mainpage.md b/doc/c/mainpage.md index 561bc93..561bc93 100644 --- a/doc/mainpage.md +++ b/doc/c/mainpage.md diff --git a/doc/c/meson.build b/doc/c/meson.build new file mode 100644 index 0000000..3ce7fdc --- /dev/null +++ b/doc/c/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +lv2_source_doc = meson.current_source_dir() + +if doxygen.found() + reference_doxygen_in = files('reference.doxygen.in') + + config = configuration_data( + { + 'LV2_SRCDIR': lv2_source_root, + 'LV2_BUILDDIR': lv2_build_root, + 'LV2_VERSION': meson.project_version(), + } + ) + + reference_doxygen = configure_file( + configuration: config, + input: reference_doxygen_in, + output: 'reference.doxygen', + ) + + docs = custom_target( + 'html', + command: [doxygen, '@INPUT@'], + input: reference_doxygen, + install: true, + install_dir: lv2_docdir, + output: ['html', 'tags'], + ) + + # TODO: doc_deps is needed because Meson did not support using custom target + # outputs as dependencies until 0.60.0. When 0.60.0 is required, this can be + # cleaned up by removing doc_deps and using lv2_tags (not its path) as a + # command argument, which Meson will correctly make a dependency for. + + lv2_tags = docs[1] + doc_deps = [docs] +else + doc_deps = [] +endif diff --git a/doc/reference.doxygen.in b/doc/c/reference.doxygen.in index 5efb066..6318484 100644 --- a/doc/reference.doxygen.in +++ b/doc/c/reference.doxygen.in @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = . +OUTPUT_DIRECTORY = @LV2_BUILDDIR@ # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -762,7 +762,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = @LV2_SRCDIR@/doc/layout.xml +LAYOUT_FILE = @LV2_SRCDIR@/doc/c/layout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -864,7 +864,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = @LV2_SRCDIR@/doc/mainpage.md \ +INPUT = @LV2_SRCDIR@/doc/c/mainpage.md \ @LV2_SRCDIR@/lv2/atom/atom.h \ @LV2_SRCDIR@/lv2/atom/forge.h \ @LV2_SRCDIR@/lv2/atom/util.h \ @@ -1045,7 +1045,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = @LV2_SRCDIR@/doc/mainpage.md +USE_MDFILE_AS_MAINPAGE = @LV2_SRCDIR@/doc/c/mainpage.md #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1167,7 +1167,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = html +HTML_OUTPUT = @LV2_BUILDDIR@/doc/c/html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). @@ -1194,7 +1194,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = @LV2_SRCDIR@/doc/header.html +HTML_HEADER = @LV2_SRCDIR@/doc/c/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1204,7 +1204,7 @@ HTML_HEADER = @LV2_SRCDIR@/doc/header.html # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = @LV2_SRCDIR@/doc/footer.html +HTML_FOOTER = @LV2_SRCDIR@/doc/c/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1216,7 +1216,7 @@ HTML_FOOTER = @LV2_SRCDIR@/doc/footer.html # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = @LV2_SRCDIR@/doc/doxy-style.css +HTML_STYLESHEET = @LV2_SRCDIR@/doc/c/doxy-style.css # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -2270,7 +2270,7 @@ TAGFILES = # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = tags +GENERATE_TAGFILE = @LV2_BUILDDIR@/doc/c/tags # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..230211e --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,23 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +if doxygen.found() + aux_files = files( + 'pygments.css', + 'style.css' + ) + + foreach file : aux_files + configure_file( + input: file, + output: '@PLAINNAME@', + copy: true, + install_dir: lv2_docdir / 'aux') + endforeach + + subdir('ns') + + build_docs = true +else + build_docs = false +endif diff --git a/doc/ns/ext/meson.build b/doc/ns/ext/meson.build new file mode 100644 index 0000000..4a2ca1f --- /dev/null +++ b/doc/ns/ext/meson.build @@ -0,0 +1,13 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +config = configuration_data({'BASE': '/ns/ext'}) + +if get_option('online_docs') + htaccess = configure_file( + configuration: config, + input: files('..' / '..' / 'htaccess.in'), + install_dir: lv2_docdir / 'ns' / 'ext', + output: '.htaccess', + ) +endif diff --git a/doc/ns/extensions/meson.build b/doc/ns/extensions/meson.build new file mode 100644 index 0000000..b54e3d2 --- /dev/null +++ b/doc/ns/extensions/meson.build @@ -0,0 +1,13 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +config = configuration_data({'BASE': '/ns/extensions'}) + +if get_option('online_docs') + htaccess = configure_file( + configuration: config, + input: files('..' / '..' / 'htaccess.in'), + install_dir: lv2_docdir / 'ns' / 'extensions', + output: '.htaccess', + ) +endif diff --git a/doc/ns/meson.build b/doc/ns/meson.build new file mode 100644 index 0000000..26471b9 --- /dev/null +++ b/doc/ns/meson.build @@ -0,0 +1,33 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +config = configuration_data({'BASE': '/ns'}) + +if get_option('online_docs') + htaccess = configure_file( + configuration: config, + input: files('..' / 'htaccess.in'), + install_dir: lv2_docdir / 'ns', + output: '.htaccess', + ) +endif + +subdir('ext') +subdir('extensions') + +lv2_build_index = files(lv2_source_root / 'scripts' / 'lv2_build_index.py') + +index = custom_target( + 'index.html', + capture: true, + command: [ + lv2_build_index, + '--lv2-version', meson.project_version(), + '--lv2-source-root', lv2_source_root, + '@INPUT@' + ], + input: spec_files, + install: true, + install_dir: lv2_docdir / 'ns', + output: 'index.html', +) diff --git a/lv2.pc.in b/lv2.pc.in deleted file mode 100644 index bfc1d14..0000000 --- a/lv2.pc.in +++ /dev/null @@ -1,7 +0,0 @@ -prefix=@PREFIX@ -includedir=@INCLUDEDIR@ - -Name: LV2 -Version: @VERSION@ -Description: An extensible audio plugin interface. -Cflags: -I${includedir} diff --git a/lv2/atom/meson.build b/lv2/atom/meson.build new file mode 100644 index 0000000..3dc43b3 --- /dev/null +++ b/lv2/atom/meson.build @@ -0,0 +1,61 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'atom' +path = 'ns' / 'ext' / name + +atom_data = files( + 'atom.meta.ttl', + 'atom.ttl', + 'manifest.ttl', +) + +headers = files( + 'atom.h', + 'forge.h', + 'util.h', +) + +tests = [ + 'atom-test', + 'forge-overflow-test', +] + +# Install specification bundle +install_data(atom_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build and run tests +if not get_option('tests').disabled() + foreach test : tests + test(test, + executable( + test, + files('@0@.c'.format(test)), + c_args: c_suppressions, + include_directories: include_directories('../../'), + ), + suite: 'unit') + endforeach +endif + +# Build documentation +if build_docs + custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('atom.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/buf-size/meson.build b/lv2/buf-size/meson.build new file mode 100644 index 0000000..c0b3a5e --- /dev/null +++ b/lv2/buf-size/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'buf-size' +path = 'ns' / 'ext' / name + +buf_size_data = files( + 'buf-size.meta.ttl', + 'buf-size.ttl', + 'manifest.ttl', +) + +headers = files( + 'buf-size.h', +) + +# Install extension bundle +install_data(buf_size_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_buf_size_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('buf-size.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/core/meson.build b/lv2/core/meson.build new file mode 100644 index 0000000..a629d0a --- /dev/null +++ b/lv2/core/meson.build @@ -0,0 +1,44 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'core' +path = 'ns' / 'lv2core' + +core_data = files( + 'lv2core.meta.ttl', + 'lv2core.ttl', + 'manifest.ttl', + 'meta.ttl', + 'people.ttl', +) + +headers = files( + 'attributes.h', + 'lv2.h', + 'lv2_util.h', +) + +# Install specification bundle +install_data(core_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / 'ns' / 'lv2core') +endif + +# Build documentation +if build_docs + lv2_core_docs = custom_target( + 'lv2core.html', + command: lv2specgen_command_prefix + [ + '--docdir=../html', + '--style-uri=../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('lv2core.ttl'), + install: true, + install_dir: lv2_docdir / 'ns', + output: 'lv2core.html', + ) +endif diff --git a/lv2/core/meta.ttl b/lv2/core/meta.ttl index d85f539..34cfa4f 100644 --- a/lv2/core/meta.ttl +++ b/lv2/core/meta.ttl @@ -46,6 +46,8 @@ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH R rdfs:label "Remove archaic properties from foaf vocabulary." ] , [ rdfs:label "Replace canonical dcs ontology with a minimal version for LV2." + ] , [ + rdfs:label "Switch to Meson build system." ] ] ] , [ diff --git a/lv2/data-access/meson.build b/lv2/data-access/meson.build new file mode 100644 index 0000000..05b086b --- /dev/null +++ b/lv2/data-access/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'data-access' +path = 'ns' / 'ext' / 'data-access' + +data_access_data = files( + 'data-access.meta.ttl', + 'data-access.ttl', + 'manifest.ttl', +) + +headers = files( + 'data-access.h', +) + +# Install specification bundle +install_data(data_access_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_data_access_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('data-access.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/dynmanifest/meson.build b/lv2/dynmanifest/meson.build new file mode 100644 index 0000000..ba78972 --- /dev/null +++ b/lv2/dynmanifest/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'dynmanifest' +path = 'ns' / 'ext' / 'dynmanifest' + +dynmanifest_data = files( + 'dynmanifest.meta.ttl', + 'dynmanifest.ttl', + 'manifest.ttl', +) + +headers = files( + 'dynmanifest.h', +) + +# Install specification bundle +install_data(dynmanifest_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_dynmanifest_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('dynmanifest.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/event/meson.build b/lv2/event/meson.build new file mode 100644 index 0000000..020acc8 --- /dev/null +++ b/lv2/event/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'event' +path = 'ns' / 'ext' / 'event' + +event_data = files( + 'event.meta.ttl', + 'event.ttl', + 'manifest.ttl', +) + +headers = files( + 'event-helpers.h', + 'event.h', +) + +# Install specification bundle +install_data(event_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_event_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('event.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/instance-access/meson.build b/lv2/instance-access/meson.build new file mode 100644 index 0000000..70ff48e --- /dev/null +++ b/lv2/instance-access/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'instance-access' +path = 'ns' / 'ext' / 'instance-access' + +instance_access_data = files( + 'instance-access.meta.ttl', + 'instance-access.ttl', + 'manifest.ttl', +) + +headers = files( + 'instance-access.h', +) + +# Install specification bundle +install_data(instance_access_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_instance_access_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('instance-access.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/log/meson.build b/lv2/log/meson.build new file mode 100644 index 0000000..9b13db0 --- /dev/null +++ b/lv2/log/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'log' +path = 'ns' / 'ext' / 'log' + +log_data = files( + 'log.meta.ttl', + 'log.ttl', + 'manifest.ttl', +) + +headers = files( + 'log.h', + 'logger.h', +) + +# Install specification bundle +install_data(log_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_log_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('log.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/meson.build b/lv2/meson.build new file mode 100644 index 0000000..d8875eb --- /dev/null +++ b/lv2/meson.build @@ -0,0 +1,52 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +dirs = [ + 'atom', + 'buf-size', + 'core', + 'data-access', + 'dynmanifest', + 'event', + 'instance-access', + 'log', + 'midi', + 'morph', + 'options', + 'parameters', + 'patch', + 'port-groups', + 'port-props', + 'presets', + 'resize-port', + 'state', + 'time', + 'ui', + 'units', + 'uri-map', + 'urid', + 'worker', +] + +foreach dir : dirs + subdir(dir) +endforeach + +if not get_option('tests').disabled() + check_python = pymod.find_installation('python3', + modules: ['rdflib'], + required: get_option('tests')) + + if check_python.found() + lv2_check_specification = files( + lv2_source_root / 'scripts' / 'lv2_check_specification.py' + ) + + foreach dir : dirs + test(dir, + lv2_check_specification, + args: files(dir / 'manifest.ttl'), + suite: ['spec']) + endforeach + endif +endif diff --git a/lv2/midi/meson.build b/lv2/midi/meson.build new file mode 100644 index 0000000..7907dfa --- /dev/null +++ b/lv2/midi/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'midi' +path = 'ns' / 'ext' / 'midi' + +midi_data = files( + 'midi.meta.ttl', + 'midi.ttl', + 'manifest.ttl', +) + +headers = files( + 'midi.h', +) + +# Install specification bundle +install_data(midi_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_midi_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('midi.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/morph/meson.build b/lv2/morph/meson.build new file mode 100644 index 0000000..0742c03 --- /dev/null +++ b/lv2/morph/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'morph' +path = 'ns' / 'ext' / 'morph' + +morph_data = files( + 'morph.meta.ttl', + 'morph.ttl', + 'manifest.ttl', +) + +headers = files( + 'morph.h', +) + +# Install specification bundle +install_data(morph_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_morph_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('morph.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/options/meson.build b/lv2/options/meson.build new file mode 100644 index 0000000..5644b87 --- /dev/null +++ b/lv2/options/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'options' +path = 'ns' / 'ext' / 'options' + +options_data = files( + 'options.meta.ttl', + 'options.ttl', + 'manifest.ttl', +) + +headers = files( + 'options.h', +) + +# Install specification bundle +install_data(options_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_options_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('options.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/parameters/meson.build b/lv2/parameters/meson.build new file mode 100644 index 0000000..ae50866 --- /dev/null +++ b/lv2/parameters/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'parameters' +path = 'ns' / 'ext' / 'parameters' + +parameters_data = files( + 'parameters.meta.ttl', + 'parameters.ttl', + 'manifest.ttl', +) + +headers = files( + 'parameters.h', +) + +# Install specification bundle +install_data(parameters_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_parameters_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('parameters.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/patch/meson.build b/lv2/patch/meson.build new file mode 100644 index 0000000..cb54fb6 --- /dev/null +++ b/lv2/patch/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'patch' +path = 'ns' / 'ext' / 'patch' + +patch_data = files( + 'patch.meta.ttl', + 'patch.ttl', + 'manifest.ttl', +) + +headers = files( + 'patch.h', +) + +# Install specification bundle +install_data(patch_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_patch_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('patch.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/port-groups/meson.build b/lv2/port-groups/meson.build new file mode 100644 index 0000000..816109f --- /dev/null +++ b/lv2/port-groups/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'port-groups' +path = 'ns' / 'ext' / 'port-groups' + +port_groups_data = files( + 'port-groups.meta.ttl', + 'port-groups.ttl', + 'manifest.ttl', +) + +headers = files( + 'port-groups.h', +) + +# Install specification bundle +install_data(port_groups_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_port_groups_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('port-groups.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/port-props/meson.build b/lv2/port-props/meson.build new file mode 100644 index 0000000..900b637 --- /dev/null +++ b/lv2/port-props/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'port-props' +path = 'ns' / 'ext' / 'port-props' + +port_props_data = files( + 'port-props.meta.ttl', + 'port-props.ttl', + 'manifest.ttl', +) + +headers = files( + 'port-props.h', +) + +# Install specification bundle +install_data(port_props_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_port_props_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('port-props.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/presets/meson.build b/lv2/presets/meson.build new file mode 100644 index 0000000..a3f2feb --- /dev/null +++ b/lv2/presets/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'presets' +path = 'ns' / 'ext' / 'presets' + +presets_data = files( + 'presets.meta.ttl', + 'presets.ttl', + 'manifest.ttl', +) + +headers = files( + 'presets.h', +) + +# Install specification bundle +install_data(presets_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_presets_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('presets.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/resize-port/meson.build b/lv2/resize-port/meson.build new file mode 100644 index 0000000..cd18a2c --- /dev/null +++ b/lv2/resize-port/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'resize-port' +path = 'ns' / 'ext' / 'resize-port' + +resize_port_data = files( + 'resize-port.meta.ttl', + 'resize-port.ttl', + 'manifest.ttl', +) + +headers = files( + 'resize-port.h', +) + +# Install specification bundle +install_data(resize_port_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_resize_port_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('resize-port.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/state/meson.build b/lv2/state/meson.build new file mode 100644 index 0000000..7914797 --- /dev/null +++ b/lv2/state/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'state' +path = 'ns' / 'ext' / 'state' + +state_data = files( + 'state.meta.ttl', + 'state.ttl', + 'manifest.ttl', +) + +headers = files( + 'state.h', +) + +# Install specification bundle +install_data(state_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_state_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('state.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/time/meson.build b/lv2/time/meson.build new file mode 100644 index 0000000..5f47e89 --- /dev/null +++ b/lv2/time/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'time' +path = 'ns' / 'ext' / 'time' + +time_data = files( + 'time.meta.ttl', + 'time.ttl', + 'manifest.ttl', +) + +headers = files( + 'time.h', +) + +# Install specification bundle +install_data(time_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_time_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('time.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/ui/meson.build b/lv2/ui/meson.build new file mode 100644 index 0000000..3eb4223 --- /dev/null +++ b/lv2/ui/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'ui' +path = 'ns' / 'extensions' / 'ui' + +ui_data = files( + 'ui.meta.ttl', + 'ui.ttl', + 'manifest.ttl', +) + +headers = files( + 'ui.h', +) + +# Install specification bundle +install_data(ui_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_ui_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('ui.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'extensions', + output: name + '.html', + ) +endif diff --git a/lv2/units/meson.build b/lv2/units/meson.build new file mode 100644 index 0000000..00d50dc --- /dev/null +++ b/lv2/units/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'units' +path = 'ns' / 'extensions' / 'units' + +units_data = files( + 'units.meta.ttl', + 'units.ttl', + 'manifest.ttl', +) + +headers = files( + 'units.h', +) + +# Install specification bundle +install_data(units_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_units_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('units.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'extensions', + output: name + '.html', + ) +endif diff --git a/lv2/uri-map/meson.build b/lv2/uri-map/meson.build new file mode 100644 index 0000000..1961645 --- /dev/null +++ b/lv2/uri-map/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'uri-map' +path = 'ns' / 'ext' / 'uri-map' + +uri_map_data = files( + 'uri-map.meta.ttl', + 'uri-map.ttl', + 'manifest.ttl', +) + +headers = files( + 'uri-map.h', +) + +# Install specification bundle +install_data(uri_map_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_uri_map_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('uri-map.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/urid/meson.build b/lv2/urid/meson.build new file mode 100644 index 0000000..38efe4b --- /dev/null +++ b/lv2/urid/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'urid' +path = 'ns' / 'ext' / 'urid' + +urid_data = files( + 'urid.meta.ttl', + 'urid.ttl', + 'manifest.ttl', +) + +headers = files( + 'urid.h', +) + +# Install specification bundle +install_data(urid_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_urid_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('urid.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2/worker/meson.build b/lv2/worker/meson.build new file mode 100644 index 0000000..248c594 --- /dev/null +++ b/lv2/worker/meson.build @@ -0,0 +1,40 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +name = 'worker' +path = 'ns' / 'ext' / 'worker' + +worker_data = files( + 'worker.meta.ttl', + 'worker.ttl', + 'manifest.ttl', +) + +headers = files( + 'worker.h', +) + +# Install specification bundle +install_data(worker_data, install_dir: lv2dir / name + '.lv2') +install_headers(headers, subdir: 'lv2' / name) +if get_option('old_headers') + install_headers(headers, subdir: 'lv2' / 'lv2plug.in' / path) +endif + +# Build documentation +if build_docs + lv2_worker_docs = custom_target( + name + '.html', + command: lv2specgen_command_prefix + [ + '--docdir=../../html', + '--style-uri=../../aux/style.css', + '@INPUT@', + '@OUTPUT@', + ], + depends: doc_deps, + input: files('worker.ttl'), + install: true, + install_dir: lv2_docdir / 'ns' / 'ext', + output: name + '.html', + ) +endif diff --git a/lv2specgen/lv2specgen.py b/lv2specgen/lv2specgen.py index ceeefbd..a07bdf3 100755 --- a/lv2specgen/lv2specgen.py +++ b/lv2specgen/lv2specgen.py @@ -1235,116 +1235,6 @@ def load_tags(path, docdir): return linkmap -def writeIndex(model, index_path, root_path, root_uri, online): - # Get extension URI - ext_node = model.value(None, rdf.type, lv2.Specification) - if not ext_node: - ext_node = model.value(None, rdf.type, owl.Ontology) - if not ext_node: - print("no extension found in %s" % bundle) - sys.exit(1) - - ext = str(ext_node) - - # Get version - minor = 0 - micro = 0 - try: - minor = int(model.value(ext_node, lv2.minorVersion, None)) - micro = int(model.value(ext_node, lv2.microVersion, None)) - except Exception: - print("warning: %s: failed to find version for %s" % (bundle, ext)) - - # Get date - date = None - for r in model.triples([ext_node, doap.release, None]): - revision = model.value(r[2], doap.revision, None) - if str(revision) == ("%d.%d" % (minor, micro)): - date = model.value(r[2], doap.created, None) - break - - # Verify that this date is the latest - if date is None: - print("warning: %s has no doap:created date" % ext_node) - else: - for r in model.triples([ext_node, doap.release, None]): - this_date = model.value(r[2], doap.created, None) - if this_date is None: - print( - "warning: %s has no doap:created date" - % (ext_node, minor, micro, date) - ) - continue - - if this_date > date: - print( - "warning: %s revision %d.%d (%s) is not the latest release" - % (ext_node, minor, micro, date) - ) - break - - # Get name and short description - name = model.value(ext_node, doap.name, None) - shortdesc = model.value(ext_node, doap.shortdesc, None) - - # Chop 'LV2' prefix from name for cleaner index - if name.startswith("LV2 "): - name = name[4:] - - # Find relative link target - if root_uri and ext_node.startswith(root_uri): - target = ext_node[len(root_uri) :] - else: - target = os.path.relpath(ext_node, root_path) - - if not online: - target += ".html" - - stem = os.path.splitext(os.path.basename(target))[0] - - # Specification (comment is to act as a sort key) - row = '<tr><!-- %s --><td><a rel="rdfs:seeAlso" href="%s">%s</a></td>' % ( - stem, - target, - name, - ) - - # API - row += "<td>" - row += '<a rel="rdfs:seeAlso" href="../doc/html/group__%s.html">%s</a>' % ( - stem, - name, - ) - row += "</td>" - - # Description - if shortdesc: - row += "<td>" + str(shortdesc) + "</td>" - else: - row += "<td></td>" - - # Version - version_str = "%s.%s" % (minor, micro) - if minor == 0 or (micro % 2 != 0): - row += '<td><span style="color: red">' + version_str + "</span></td>" - else: - row += "<td>" + version_str + "</td>" - - # Status - deprecated = model.value(ext_node, owl.deprecated, None) - if minor == 0: - row += '<td><span class="error">Experimental</span></td>' - elif deprecated and str(deprecated[2]) != "false": - row += '<td><span class="warning">Deprecated</span></td>' - elif micro % 2 == 0: - row += '<td><span class="success">Stable</span></td>' - - row += "</tr>" - - with open(index_path, "w") as index: - index.write(row) - - def specgen( specloc, indir, @@ -1353,10 +1243,6 @@ def specgen( tags, opts, instances=False, - root_link=None, - index_path=None, - root_path=None, - root_uri=None, ): """The meat and potatoes: Everything starts here.""" @@ -1483,8 +1369,6 @@ def specgen( name = specProperty(m, spec, doap.name) title = name - if root_link: - name = '<a href="%s">%s</a>' % (root_link, name) template = template.replace("@TITLE@", title) template = template.replace("@NAME@", name) @@ -1557,10 +1441,6 @@ def specgen( template = template.replace("@DATE@", build_date.strftime("%F")) template = template.replace("@TIME@", build_date.strftime("%F %H:%M UTC")) - # Write index row - if index_path is not None: - writeIndex(m, index_path, root_path, root_uri, opts["online"]) - # Validate complete output page try: oldcwd = os.getcwd() @@ -1667,13 +1547,6 @@ if __name__ == "__main__": help="Doxygen output directory", ) opt.add_option( - "--index", - type="string", - dest="index_path", - default=None, - help="Index row output file", - ) - opt.add_option( "--tags", type="string", dest="tags", @@ -1681,22 +1554,6 @@ if __name__ == "__main__": help="Doxygen tags file", ) opt.add_option( - "-r", - "--root-path", - type="string", - dest="root_path", - default="", - help="Root path", - ) - opt.add_option( - "-R", - "--root-uri", - type="string", - dest="root_uri", - default="", - help="Root URI", - ) - opt.add_option( "-p", "--prefix", type="string", @@ -1716,13 +1573,6 @@ if __name__ == "__main__": dest="copy_style", help="Copy style from template directory to output directory", ) - opt.add_option( - "-o", - "--online", - action="store_true", - dest="online", - help="Generate index for online documentation", - ) (options, args) = opt.parse_args() opts = vars(options) @@ -1734,7 +1584,6 @@ if __name__ == "__main__": spec_pre = options.prefix ontology = "file:" + str(args[0]) output = args[1] - index_path = options.index_path docdir = options.docdir tags = options.tags @@ -1750,11 +1599,6 @@ if __name__ == "__main__": print("warning: extension %s has no %s.ttl file" % (b, b)) sys.exit(1) - # Root link - root_path = opts["root_path"] - root_uri = opts["root_uri"] - root_link = os.path.join(root_path, "index.html") - # Generate spec documentation specdoc = specgen( spec, @@ -1764,10 +1608,6 @@ if __name__ == "__main__": tags, opts, instances=True, - root_link=root_link, - index_path=index_path, - root_path=root_path, - root_uri=root_uri, ) # Save to HTML output file diff --git a/lv2specgen/meson.build b/lv2specgen/meson.build new file mode 100644 index 0000000..578071f --- /dev/null +++ b/lv2specgen/meson.build @@ -0,0 +1,27 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +lv2specgen_py = files('lv2specgen.py') + +lv2_list_email = 'devel@lists.lv2plug.in' +lv2_list_page = 'http://lists.lv2plug.in/listinfo.cgi/devel-lv2plug.in' + +lv2specgen_command_prefix = [ + lv2specgen_py, + '--list-email=' + lv2_list_email, + '--list-page=' + lv2_list_page, +] + +if is_variable('lv2_tags') + lv2specgen_command_prefix += [ + '--tags', lv2_tags.full_path(), # TODO: Remove full_path() in meson 0.60.0 + ] +endif + +install_data( + files('lv2specgen.py'), + install_dir: get_option('bindir'), + install_mode: 'rwxr-xr-x', +) + +meson.override_find_program('lv2specgen.py', lv2specgen_py) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5fbf3ba --- /dev/null +++ b/meson.build @@ -0,0 +1,194 @@ +# Copyright 2021-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +project('lv2', ['c'], + version: '1.18.5', + license: 'ISC', + meson_version: '>= 0.56.0', + default_options: [ + 'b_ndebug=if-release', + 'buildtype=release', + 'c_std=c99', + ]) + +lv2_docdir = get_option('datadir') / 'doc' / 'lv2' +lv2_source_root = meson.current_source_dir() +lv2_build_root = meson.current_build_dir() + +####################### +# Compilers and Flags # +####################### + +# Required tools +pkg = import('pkgconfig') +pymod = import('python') +cc = meson.get_compiler('c') + +# Optional C++ compiler and Python tools for tests +if not get_option('tests').disabled() + if add_languages(['cpp'], native: false, required: get_option('tests')) + cpp = meson.get_compiler('cpp') + endif +endif + +# Set global warning flags +if get_option('strict') and not meson.is_subproject() + subdir('meson/warnings') +endif +subdir('meson/suppressions') + +########################## +# LV2 Path Configuration # +########################## + +lv2dir = get_option('lv2dir') +if lv2dir == '' + prefix = get_option('prefix') + if target_machine.system() == 'darwin' and prefix == '/' + lv2dir = '/Library/Audio/Plug-Ins/LV2' + elif target_machine.system() == 'haiku' and prefix == '/' + lv2dir = '/boot/common/add-ons/lv2' + elif target_machine.system() == 'windows' and prefix == 'C:/' + lv2dir = 'C:/Program Files/Common/LV2' + else + lv2dir = prefix / get_option('libdir') / 'lv2' + endif +endif + +###################### +# Package/Dependency # +###################### + +# Generage pkg-config file for external dependants +pkg.generate( + name: 'LV2', + filebase: 'lv2', + subdirs: ['lv2'], + version: meson.project_version(), + description: 'Plugin standard for audio systems') + +# Declare dependency for internal meson dependants +lv2_dep = declare_dependency( + include_directories: include_directories('.'), + version: meson.project_version()) + +################## +# Specifications # +################## + +doc_python_modules = [ + 'lxml', + 'markdown', + 'pygments', + 'rdflib', +] + +# Determine if all the dependencies for building documentation are present +build_docs = false +doc_deps = [] +if not get_option('docs').disabled() + doxygen = find_program('doxygen', required: get_option('docs')) + + python = pymod.find_installation( + 'python3', + modules: doc_python_modules, + required: get_option('docs'), + ) + + build_docs = doxygen.found() and python.found() +endif + +# Basic scripts and schema data +subdir('scripts') +subdir('schemas.lv2') + +# Run Doxygen to generate tags file and HTML code documentation +if build_docs + subdir('doc/c') +endif + +# Set up lv2specgen for generating individual specification documentation +subdir('lv2specgen') + +# Specifications (and their individual documentation) +subdir('lv2') +spec_files = (atom_data + + buf_size_data + + core_data + + data_access_data + + dynmanifest_data + + event_data + + instance_access_data + + log_data + + midi_data + + morph_data + + options_data + + parameters_data + + patch_data + + port_groups_data + + port_props_data + + presets_data + + resize_port_data + + state_data + + time_data + + ui_data + + units_data + + uri_map_data + + urid_data + + worker_data) + +# Plugins and "Programming LV2 Plugins" book +if not get_option('plugins').disabled() + subdir('plugins') +endif + +############ +# Programs # +############ + +# Command-line utilities +subdir('util') + +# Data and build tests +subdir('test') + +################# +# Documentation # +################# + +# Top-level documentation +if build_docs + subdir('doc') +endif + +######## +# News # +######## + +lv2_write_news_py = find_program('scripts' / 'lv2_write_news.py') + +write_news_command = [ + lv2_write_news_py, + '-t', 'http://lv2plug.in/ns/lv2', + files(lv2_source_root / 'lv2' / 'core.lv2' / 'people.ttl'), + files(lv2_source_root / 'lv2' / 'core.lv2' / 'meta.ttl'), + spec_files, +] + +custom_target( + 'NEWS', + capture: true, + command: write_news_command, + output: 'NEWS', +) + +if not meson.is_subproject() + # Generate NEWS file from data in distribution archive + meson.add_dist_script(write_news_command) + + summary('Tests', not get_option('tests').disabled(), bool_yn: true) + summary('Documentation', build_docs, bool_yn: true) + summary('Prefix', get_option('prefix'), section: 'Paths') + summary('LV2 bundles', lv2dir, section: 'Paths') + summary('Headers', get_option('prefix') / get_option('includedir'), section: 'Paths') +endif diff --git a/meson/library/meson.build b/meson/library/meson.build new file mode 100644 index 0000000..f50505f --- /dev/null +++ b/meson/library/meson.build @@ -0,0 +1,30 @@ +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +# General definitions for building libraries. +# +# These are essentially workarounds for meson and/or MSVC. Unfortunately, +# meson's default_library option doesn't support shared and static builds very +# well. In particular, it's often necessary to define different symbols for +# static and shared builds of libraries so that symbols can be exported. To +# work around this, we do not support default_library=both on Windows. On +# other platforms with GCC-like compilers, we can support both because symbols +# can safely be exported in the same way (giving them default visibility) in +# both static and shared builds. + +# Abort on Windows with default_library=both +if get_option('default_library') == 'both' + if host_machine.system() == 'windows' + error('default_library=both is not supported on Windows') + endif +endif + +# Set library_suffix to the suffix for libraries +if cc.get_id() == 'msvc' + # Meson appends a version to the name only on MS, which leads to inconsistent + # library names, like `mylib-1-1`. So, provide no suffix to ultimately get + # the same name as on other platforms, like `mylib-1`. + library_suffix = '' +else + library_suffix = '-@0@'.format(meson.project_version().split('.')[0]) +endif diff --git a/meson/suppressions/meson.build b/meson/suppressions/meson.build new file mode 100644 index 0000000..fcce3f5 --- /dev/null +++ b/meson/suppressions/meson.build @@ -0,0 +1,129 @@ +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +# Project-specific warning suppressions. +# +# This should be used in conjunction with the generic "warnings" sibling that +# enables all reasonable warnings for the compiler. It lives here just to keep +# the top-level meson.build more readable. + +##### +# C # +##### + +if is_variable('cc') + c_suppressions = [] + + if get_option('strict') + if cc.get_id() in ['clang', 'emscripten'] + c_suppressions += [ + '-Wno-cast-align', + '-Wno-cast-qual', + '-Wno-documentation-unknown-command', + '-Wno-double-promotion', + '-Wno-float-conversion', + '-Wno-float-equal', + '-Wno-implicit-float-conversion', + '-Wno-padded', + '-Wno-reserved-id-macro', + '-Wno-shorten-64-to-32', + '-Wno-sign-conversion', + '-Wno-switch-enum', + '-Wno-unused-parameter', + ] + elif cc.get_id() == 'gcc' + c_suppressions += [ + '-Wno-cast-align', + '-Wno-cast-qual', + '-Wno-conversion', + '-Wno-double-promotion', + '-Wno-float-equal', + '-Wno-inline', + '-Wno-padded', + '-Wno-suggest-attribute=const', + '-Wno-suggest-attribute=malloc', + '-Wno-suggest-attribute=pure', + '-Wno-switch-default', + '-Wno-switch-enum', + '-Wno-unsuffixed-float-constants', + '-Wno-unused-const-variable', + '-Wno-unused-parameter', + ] + + if target_machine.system() == 'windows' + c_suppressions += [ + '-Wno-suggest-attribute=format', + ] + endif + + elif cc.get_id() == 'msvc' + c_suppressions += [ + '/wd4061', # enumerator in switch is not explicitly handled + '/wd4100', # unreferenced formal parameter + '/wd4244', # conversion with possible loss of data + '/wd4267', # conversion from size_t to a smaller type + '/wd4310', # cast truncates constant value + '/wd4365', # signed/unsigned mismatch + '/wd4464', # relative include path contains ".." + '/wd4514', # unreferenced inline function has been removed + '/wd4514', # unreferenced inline function has been removed + '/wd4706', # assignment within conditional expression + '/wd4710', # function not inlined + '/wd4711', # function selected for automatic inline expansion + '/wd4820', # padding added after construct + '/wd5045', # will insert Spectre mitigation for memory load + ] + endif + endif + + c_suppressions = cc.get_supported_arguments(c_suppressions) +endif + +####### +# C++ # +####### + +if is_variable('cpp') + cpp_suppressions = [] + + if get_option('strict') + if cpp.get_id() in ['clang', 'emscripten'] + cpp_suppressions = [ + '-Wno-cast-align', + '-Wno-cast-qual', + '-Wno-documentation-unknown-command', + '-Wno-nullability-extension', + '-Wno-padded', + '-Wno-reserved-id-macro', + ] + + elif cpp.get_id() == 'gcc' + cpp_suppressions = [ + '-Wno-cast-align', + '-Wno-cast-qual', + '-Wno-inline', + '-Wno-padded', + '-Wno-unused-const-variable', + '-Wno-useless-cast', + ] + + if target_machine.system() == 'windows' + cpp_suppressions += [ + '-Wno-suggest-attribute=format', + ] + endif + + elif cpp.get_id() == 'msvc' + cpp_suppressions = [ + '/wd4514', # unreferenced inline function has been removed + '/wd4706', # assignment within conditional expression + '/wd4710', # function not inlined + '/wd4711', # function selected for automatic inline expansion + '/wd4820', # padding added after data member + '/wd5045', # will insert Spectre mitigation + ] + endif + endif + + cpp_suppressions = cpp.get_supported_arguments(cpp_suppressions) +endif diff --git a/meson/warnings/meson.build b/meson/warnings/meson.build new file mode 100644 index 0000000..e0051f9 --- /dev/null +++ b/meson/warnings/meson.build @@ -0,0 +1,256 @@ +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +# General code to enable approximately all warnings in GCC 12, clang, and MSVC. +# +# This is trivial for clang and MSVC, but GCC doesn't have an "everything" +# option, so we need to enable everything we want explicitly. Wall is assumed, +# but Wextra is not, for stability. +# +# These are collected from common.opt and c.opt in the GCC source, and manually +# curated with the help of the GCC documentation. Warnings that are +# application-specific, historical, or about compatibility between specific +# language revisions are omitted. The intent here is to have roughly the same +# meaning as clang's Weverything: extremely strict, but general. Specifically +# omitted are: +# +# General: +# +# Wabi= +# Waggregate-return +# Walloc-size-larger-than=BYTES +# Walloca-larger-than=BYTES +# Wframe-larger-than=BYTES +# Wlarger-than=BYTES +# Wstack-usage=BYTES +# Wsystem-headers +# Wtraditional +# Wtraditional-conversion +# Wtrampolines +# Wvla-larger-than=BYTES +# +# Build specific: +# +# Wpoison-system-directories +# +# C Specific: +# +# Wc11-c2x-compat +# Wc90-c99-compat +# Wc99-c11-compat +# Wdeclaration-after-statement +# Wtraditional +# Wtraditional-conversion +# +# C++ Specific: +# +# Wc++0x-compat +# Wc++1z-compat +# Wc++2a-compat +# Wctad-maybe-unsupported +# Wnamespaces +# Wtemplates + +# GCC warnings that apply to all C-family languages +gcc_common_warnings = [ + '-Walloc-zero', + '-Walloca', + '-Wanalyzer-too-complex', + '-Warith-conversion', + '-Warray-bounds=2', + '-Wattribute-alias=2', + '-Wbidi-chars=ucn', + '-Wcast-align=strict', + '-Wcast-function-type', + '-Wcast-qual', + '-Wclobbered', + '-Wconversion', + '-Wdate-time', + '-Wdisabled-optimization', + '-Wdouble-promotion', + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Wempty-body', + '-Wendif-labels', + '-Wfloat-equal', + '-Wformat-overflow=2', + '-Wformat-signedness', + '-Wformat-truncation=2', + '-Wformat=2', + '-Wignored-qualifiers', + '-Wimplicit-fallthrough=3', + '-Winit-self', + '-Winline', + '-Winvalid-pch', + '-Wlogical-op', + '-Wmissing-declarations', + '-Wmissing-field-initializers', + '-Wmissing-include-dirs', + '-Wmultichar', + '-Wnormalized=nfc', + '-Wnull-dereference', + '-Wopenacc-parallelism', + '-Woverlength-strings', + '-Wpacked', + '-Wpacked-bitfield-compat', + '-Wpadded', + '-Wpointer-arith', + '-Wredundant-decls', + '-Wshadow', + '-Wshift-negative-value', + '-Wshift-overflow=2', + '-Wstack-protector', + '-Wstrict-aliasing=3', + '-Wstrict-overflow=5', + '-Wstring-compare', + '-Wstringop-overflow=3', + '-Wsuggest-attribute=cold', + '-Wsuggest-attribute=const', + '-Wsuggest-attribute=format', + '-Wsuggest-attribute=malloc', + '-Wsuggest-attribute=noreturn', + '-Wsuggest-attribute=pure', + '-Wswitch-default', + '-Wswitch-enum', + '-Wtrampolines', + '-Wtrivial-auto-var-init', + '-Wtype-limits', + '-Wundef', + '-Wuninitialized', + '-Wunsafe-loop-optimizations', + '-Wunused', + '-Wunused-const-variable=2', + '-Wunused-macros', + '-Wvector-operation-performance', + '-Wvla', + '-Wwrite-strings', +] + +##### +# C # +##### + +if is_variable('cc') and not is_variable('all_c_warnings') + # Set all_c_warnings for the current C compiler + all_c_warnings = [] + + if get_option('strict') + if cc.get_id() == 'clang' + all_c_warnings += ['-Weverything'] + + if not meson.is_cross_build() + all_c_warnings += [ + '-Wno-poison-system-directories', + ] + endif + + elif cc.get_id() == 'gcc' + all_c_warnings += gcc_common_warnings + [ + '-Wabsolute-value', + '-Wbad-function-cast', + '-Wc++-compat', + '-Wenum-conversion', + '-Wjump-misses-init', + '-Wmissing-parameter-type', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wold-style-declaration', + '-Wold-style-definition', + '-Woverride-init', + '-Wsign-compare', + '-Wstrict-prototypes', + '-Wunsuffixed-float-constants', + ] + + elif cc.get_id() == 'msvc' + all_c_warnings += [ + '/Wall', + '/experimental:external', + '/external:W0', + '/external:anglebrackets', + ] + endif + endif + + all_c_warnings = cc.get_supported_arguments(all_c_warnings) + add_global_arguments(all_c_warnings, language: ['c']) +endif + +####### +# C++ # +####### + +if is_variable('cpp') and not is_variable('all_cpp_warnings') + # Set all_cpp_warnings for the current C++ compiler + all_cpp_warnings = [] + + if get_option('strict') + if cpp.get_id() == 'clang' + all_cpp_warnings += [ + '-Weverything', + '-Wno-c++98-compat', + '-Wno-c++98-compat-pedantic', + ] + + if not meson.is_cross_build() + all_cpp_warnings += [ + '-Wno-poison-system-directories', + ] + endif + + elif cpp.get_id() == 'gcc' + all_cpp_warnings += gcc_common_warnings + [ + '-Wabi-tag', + '-Waligned-new=all', + '-Wcatch-value=3', + '-Wcomma-subscript', + '-Wconditionally-supported', + '-Wctor-dtor-privacy', + '-Wdelete-non-virtual-dtor', + '-Wdeprecated', + '-Wdeprecated-copy', + '-Wdeprecated-copy-dtor', + '-Wdeprecated-enum-enum-conversion', + '-Wdeprecated-enum-float-conversion', + '-Weffc++', + '-Wexpansion-to-defined', + '-Wextra-semi', + '-Wimport', + '-Winvalid-imported-macros', + '-Wmismatched-tags', + '-Wmultiple-inheritance', + '-Wnoexcept', + '-Wnoexcept-type', + '-Wnon-virtual-dtor', + '-Wold-style-cast', + '-Woverloaded-virtual', + '-Wplacement-new=2', + '-Wredundant-move', + '-Wredundant-tags', + '-Wregister', + '-Wsign-compare', + '-Wsign-promo', + '-Wsized-deallocation', + '-Wstrict-null-sentinel', + '-Wsuggest-final-methods', + '-Wsuggest-final-types', + '-Wsuggest-override', + '-Wuseless-cast', + '-Wvirtual-inheritance', + '-Wvolatile', + '-Wzero-as-null-pointer-constant', + ] + + elif cpp.get_id() == 'msvc' + all_cpp_warnings += [ + '/Wall', + '/experimental:external', + '/external:W0', + '/external:anglebrackets', + ] + endif + endif + + all_cpp_warnings = cpp.get_supported_arguments(all_cpp_warnings) + add_global_arguments(all_cpp_warnings, language: ['cpp']) +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..089402e --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,23 @@ +option('docs', type: 'feature', value: 'auto', yield: true, + description: 'Build documentation') + +option('lv2dir', type: 'string', value: '', yield: true, + description: 'LV2 bundle installation directory') + +option('old_headers', type: 'boolean', value: true, yield: true, + description: 'Install backwards compatible headers at URI-style paths') + +option('online_docs', type: 'boolean', value: 'false', yield: true, + description: 'Build documentation for online hosting') + +option('plugins', type: 'feature', value: 'auto', yield: true, + description: 'Build example plugins') + +option('strict', type: 'boolean', value: false, yield: true, + description: 'Enable ultra-strict warnings') + +option('tests', type: 'feature', value: 'auto', yield: true, + description: 'Build tests') + +option('title', type: 'string', value: 'LV2', + description: 'Project title') diff --git a/plugins/eg-amp.lv2/meson.build b/plugins/eg-amp.lv2/meson.build new file mode 100644 index 0000000..2b15b01 --- /dev/null +++ b/plugins/eg-amp.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('amp.c') +bundle_name = 'eg-amp.lv2' +data_filenames = ['manifest.ttl.in', 'amp.ttl'] + +module = shared_library( + 'amp', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-amp.lv2/waf b/plugins/eg-amp.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-amp.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf
\ No newline at end of file diff --git a/plugins/eg-amp.lv2/wscript b/plugins/eg-amp.lv2/wscript deleted file mode 100644 index 822825d..0000000 --- a/plugins/eg-amp.lv2/wscript +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-amp.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2', uselib_store='LV2') - - conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) - -def build(bld): - bundle = 'eg-amp.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-amp.lv2) - for i in ['amp.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'amp.c', - name = 'amp', - target = 'lv2/%s/amp' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - uselib = 'M LV2') diff --git a/plugins/eg-fifths.lv2/meson.build b/plugins/eg-fifths.lv2/meson.build new file mode 100644 index 0000000..fd38ee3 --- /dev/null +++ b/plugins/eg-fifths.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('fifths.c') +bundle_name = 'eg-fifths.lv2' +data_filenames = ['manifest.ttl.in', 'fifths.ttl'] + +module = shared_library( + 'fifths', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-fifths.lv2/waf b/plugins/eg-fifths.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-fifths.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf
\ No newline at end of file diff --git a/plugins/eg-fifths.lv2/wscript b/plugins/eg-fifths.lv2/wscript deleted file mode 100644 index 8b2991b..0000000 --- a/plugins/eg-fifths.lv2/wscript +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-fifths.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') - -def build(bld): - bundle = 'eg-fifths.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-fifths.lv2) - for i in ['fifths.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'fifths.c', - name = 'fifths', - target = 'lv2/%s/fifths' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'LV2') diff --git a/plugins/eg-metro.lv2/meson.build b/plugins/eg-metro.lv2/meson.build new file mode 100644 index 0000000..f881eca --- /dev/null +++ b/plugins/eg-metro.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('metro.c') +bundle_name = 'eg-metro.lv2' +data_filenames = ['manifest.ttl.in', 'metro.ttl'] + +module = shared_library( + 'metro', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-metro.lv2/waf b/plugins/eg-metro.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-metro.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf
\ No newline at end of file diff --git a/plugins/eg-metro.lv2/wscript b/plugins/eg-metro.lv2/wscript deleted file mode 100644 index 5fb0d07..0000000 --- a/plugins/eg-metro.lv2/wscript +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-metro.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2 >= 0.2.0', uselib_store='LV2') - - conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) - -def build(bld): - bundle = 'eg-metro.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-metro.lv2) - bld(features = 'subst', - is_copy = True, - source = 'metro.ttl', - target = 'lv2/%s/metro.ttl' % bundle, - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'metro.c', - name = 'metro', - target = 'lv2/%s/metro' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = ['M', 'LV2']) diff --git a/plugins/eg-midigate.lv2/meson.build b/plugins/eg-midigate.lv2/meson.build new file mode 100644 index 0000000..0e35fd1 --- /dev/null +++ b/plugins/eg-midigate.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('midigate.c') +bundle_name = 'eg-midigate.lv2' +data_filenames = ['manifest.ttl.in', 'midigate.ttl'] + +module = shared_library( + 'midigate', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-midigate.lv2/waf b/plugins/eg-midigate.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-midigate.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf
\ No newline at end of file diff --git a/plugins/eg-midigate.lv2/wscript b/plugins/eg-midigate.lv2/wscript deleted file mode 100644 index 5862721..0000000 --- a/plugins/eg-midigate.lv2/wscript +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-midigate.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2', uselib_store='LV2') - -def build(bld): - bundle = 'eg-midigate.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-midigate.lv2) - for i in ['midigate.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'midigate.c', - name = 'midigate', - target = 'lv2/%s/midigate' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - uselib = 'LV2') diff --git a/plugins/eg-params.lv2/meson.build b/plugins/eg-params.lv2/meson.build new file mode 100644 index 0000000..4c1e576 --- /dev/null +++ b/plugins/eg-params.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('params.c') +bundle_name = 'eg-params.lv2' +data_filenames = ['manifest.ttl.in', 'params.ttl'] + +module = shared_library( + 'params', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-params.lv2/wscript b/plugins/eg-params.lv2/wscript deleted file mode 100644 index 503e8db..0000000 --- a/plugins/eg-params.lv2/wscript +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-params.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2 >= 1.12.1', uselib_store='LV2') - -def build(bld): - bundle = 'eg-params.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-params.lv2) - for i in ['params.ttl']: - bld(features = 'subst', - is_copy = True, - source = i, - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'params.c', - name = 'params', - target = 'lv2/%s/params' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'LV2') diff --git a/plugins/eg-sampler.lv2/meson.build b/plugins/eg-sampler.lv2/meson.build new file mode 100644 index 0000000..85dc9a0 --- /dev/null +++ b/plugins/eg-sampler.lv2/meson.build @@ -0,0 +1,44 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('sampler.c') +bundle_name = 'eg-sampler.lv2' +data_filenames = ['manifest.ttl.in', 'sampler.ttl'] + +sndfile_dep = dependency('sndfile', + version: '>= 1.0.0', + required: get_option('plugins')) + +if sndfile_dep.found() + module = shared_library( + 'sampler', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep, sndfile_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', + ) + + extension = '.' + module.full_path().split('.')[-1] + config = configuration_data({'LIB_EXT': extension}) + + foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif + endforeach +endif diff --git a/plugins/eg-sampler.lv2/waf b/plugins/eg-sampler.lv2/waf deleted file mode 120000 index 59a1ac9..0000000 --- a/plugins/eg-sampler.lv2/waf +++ /dev/null @@ -1 +0,0 @@ -../../waf
\ No newline at end of file diff --git a/plugins/eg-sampler.lv2/wscript b/plugins/eg-sampler.lv2/wscript deleted file mode 100644 index 8c640c1..0000000 --- a/plugins/eg-sampler.lv2/wscript +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-sampler.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') - conf.check_pkg('sndfile >= 1.0.0', uselib_store='SNDFILE') - conf.check_pkg('gtk+-2.0 >= 2.18.0', - uselib_store='GTK2', - system=True, - mandatory=False) - conf.check(features='c cshlib', lib='m', uselib_store='M', mandatory=False) - -def build(bld): - bundle = 'eg-sampler.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - bld(features = 'subst', - source = 'manifest.ttl.in', - target = 'lv2/%s/%s' % (bundle, 'manifest.ttl'), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Copy other data files to build bundle (build/eg-sampler.lv2) - for i in ['sampler.ttl', 'click.wav']: - bld(features = 'subst', - is_copy = True, - source = i, - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'sampler.c', - name = 'sampler', - target = 'lv2/%s/sampler' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = ['M', 'SNDFILE', 'LV2']) - - # Build UI library - if bld.env.HAVE_GTK2: - obj = bld(features = 'c cshlib lv2lib', - source = 'sampler_ui.c', - name = 'sampler_ui', - target = 'lv2/%s/sampler_ui' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = ['GTK2', 'LV2']) diff --git a/plugins/eg-scope.lv2/meson.build b/plugins/eg-scope.lv2/meson.build new file mode 100644 index 0000000..ecf01b2 --- /dev/null +++ b/plugins/eg-scope.lv2/meson.build @@ -0,0 +1,41 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +plugin_sources = files('examploscope.c') +bundle_name = 'eg-scope.lv2' +data_filenames = ['manifest.ttl.in', 'examploscope.ttl.in'] + +module = shared_library( + 'examploscope', + plugin_sources, + c_args: c_suppressions, + dependencies: [lv2_dep, m_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: lv2dir / bundle_name, + name_prefix: '', +) + +config = configuration_data( + { + 'LIB_EXT': '.' + module.full_path().split('.')[-1], + } +) + +foreach filename : data_filenames + if filename.endswith('.in') + configure_file( + configuration: config, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename.substring(0, -3), + ) + else + configure_file( + copy: true, + input: files(filename), + install_dir: lv2dir / bundle_name, + output: filename, + ) + endif +endforeach diff --git a/plugins/eg-scope.lv2/wscript b/plugins/eg-scope.lv2/wscript deleted file mode 100644 index 4333502..0000000 --- a/plugins/eg-scope.lv2/wscript +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -from waflib.extras import autowaf as autowaf -import re - -# Variables for 'waf dist' -APPNAME = 'eg-scope.lv2' -VERSION = '1.0.0' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - opt.load('lv2') - autowaf.set_options(opt) - -def configure(conf): - conf.load('compiler_c', cache=True) - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - conf.check_pkg('lv2 >= 1.2.1', uselib_store='LV2') - conf.check_pkg('cairo >= 1.8.10', uselib_store='CAIRO') - conf.check_pkg('gtk+-2.0 >= 2.18.0', - uselib_store='GTK2', - system=True, - mandatory=False) - -def build(bld): - bundle = 'eg-scope.lv2' - - # Build manifest.ttl by substitution (for portable lib extension) - for i in ['manifest.ttl', 'examploscope.ttl']: - bld(features = 'subst', - source = i + '.in', - target = 'lv2/%s/%s' % (bundle, i), - install_path = '${LV2DIR}/%s' % bundle, - LIB_EXT = bld.env.LV2_LIB_EXT) - - # Build plugin library - obj = bld(features = 'c cshlib lv2lib', - source = 'examploscope.c', - name = 'examploscope', - target = 'lv2/%s/examploscope' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'LV2') - - # Build UI library - if bld.env.HAVE_GTK2: - obj = bld(features = 'c cshlib lv2lib', - source = 'examploscope_ui.c', - name = 'examploscope_ui', - target = 'lv2/%s/examploscope_ui' % bundle, - install_path = '${LV2DIR}/%s' % bundle, - use = 'GTK2 CAIRO LV2') diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 0000000..ee114ef --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,82 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +if not get_option('plugins').disabled() + m_dep = cc.find_library('m', required: false) + + subdir('eg-amp.lv2') + subdir('eg-fifths.lv2') + subdir('eg-metro.lv2') + subdir('eg-midigate.lv2') + subdir('eg-params.lv2') + subdir('eg-sampler.lv2') + subdir('eg-scope.lv2') +endif + +if not get_option('docs').disabled() + literasc_py = files('literasc.py') + asciidoc = find_program('asciidoc', required: get_option('docs')) + + if asciidoc.found() + book_inputs = files( + 'README.txt', + 'eg-amp.lv2/README.txt', + 'eg-amp.lv2/amp.c', + 'eg-amp.lv2/amp.ttl', + 'eg-fifths.lv2/README.txt', + 'eg-fifths.lv2/fifths.c', + 'eg-fifths.lv2/fifths.ttl', + 'eg-fifths.lv2/uris.h', + 'eg-metro.lv2/README.txt', + 'eg-metro.lv2/metro.c', + 'eg-metro.lv2/metro.ttl', + 'eg-midigate.lv2/README.txt', + 'eg-midigate.lv2/midigate.c', + 'eg-midigate.lv2/midigate.ttl', + 'eg-params.lv2/README.txt', + 'eg-params.lv2/params.c', + 'eg-params.lv2/params.ttl', + 'eg-params.lv2/state_map.h', + 'eg-sampler.lv2/README.txt', + 'eg-sampler.lv2/atom_sink.h', + 'eg-sampler.lv2/peaks.h', + 'eg-sampler.lv2/sampler.c', + 'eg-sampler.lv2/sampler.ttl', + 'eg-sampler.lv2/sampler_ui.c', + 'eg-sampler.lv2/uris.h', + 'eg-scope.lv2/README.txt', + 'eg-scope.lv2/examploscope.c', + 'eg-scope.lv2/examploscope_ui.c', + 'eg-scope.lv2/uris.h', + ) + + # Compile book sources into book.txt asciidoc source + book_txt = custom_target( + 'book.txt', + command: [ + literasc_py, + '@OUTPUT@', + '@INPUT@', + ], + input: book_inputs, + output: 'book.txt', + ) + + # Run asciidoc to generate book.html + book_html = custom_target( + 'book.html', + build_by_default: true, + command: [ + asciidoc, + '-a', 'stylesdir=' + lv2_source_root / 'doc', + '-a', 'source-highlighter=pygments', + '-a', 'pygments-style=' + lv2_source_root / 'doc' / 'style.css', + '-b', 'html', + '-o', '@OUTPUT@', + '@INPUT@', + ], + input: book_txt, + output: 'book.html', + ) + endif +endif diff --git a/plugins/wscript b/plugins/wscript deleted file mode 100644 index 590a529..0000000 --- a/plugins/wscript +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -import os - -from waflib.extras import autowaf as autowaf -import waflib.Logs as Logs - -import literasc - -def configure(conf): - pass - -def bld_book_src(task): - filenames = [] - for i in task.inputs: - filenames += [i.abspath()] - - literasc.gen(open(task.outputs[0].abspath(), 'w'), filenames) - -def build(bld): - files = [bld.path.find_node('README.txt')] - for i in ['eg-amp.lv2', - 'eg-midigate.lv2', - 'eg-fifths.lv2', - 'eg-metro.lv2', - 'eg-sampler.lv2', - 'eg-scope.lv2', - 'eg-params.lv2']: - files += bld.path.ant_glob('%s/*.txt' % i) - files += bld.path.ant_glob('%s/manifest.ttl*' % i) - files += bld.path.ant_glob('%s/*.ttl' % i) - files += bld.path.ant_glob('%s/*.c' % i) - files += bld.path.ant_glob('%s/*.h' % i) - - # Compile book sources into book.txt asciidoc source - bld(rule = bld_book_src, - source = files, - target = 'book.txt') - - # Run asciidoc to generate book.html - stylesdir = bld.path.find_node('../doc/').abspath() - pygments_style = bld.path.find_node('../doc/style.css').abspath() - bld(rule = 'asciidoc -a stylesdir=%s -a source-highlighter=pygments -a pygments-style=%s -b html -o ${TGT} ${SRC}' % ( - stylesdir, pygments_style), - source = 'book.txt', - target = 'book.html') diff --git a/schemas.lv2/meson.build b/schemas.lv2/meson.build new file mode 100644 index 0000000..fb7bed5 --- /dev/null +++ b/schemas.lv2/meson.build @@ -0,0 +1,16 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +schema_data = files( + 'dcs.ttl', + 'dcterms.ttl', + 'doap.ttl', + 'foaf.ttl', + 'manifest.ttl', + 'owl.ttl', + 'rdf.ttl', + 'rdfs.ttl', + 'xsd.ttl', +) + +install_data(schema_data, install_dir: lv2dir / 'schemas.lv2') diff --git a/scripts/lv2_build_index.py b/scripts/lv2_build_index.py new file mode 100755 index 0000000..444e078 --- /dev/null +++ b/scripts/lv2_build_index.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 + +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: ISC + +""" +Write an HTML index for a set of LV2 specifications. +""" + +import datetime +import json +import os +import time +import sys +import argparse +import subprocess + +import rdflib + + +doap = rdflib.Namespace("http://usefulinc.com/ns/doap#") +lv2 = rdflib.Namespace("http://lv2plug.in/ns/lv2core#") +owl = rdflib.Namespace("http://www.w3.org/2002/07/owl#") +rdf = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#") + + +def _subst_file(template_path, output_file, substitutions): + "Replace keys with values in a template file and write the result." + + with open(template_path, "r", encoding="utf-8") as template: + for line in template: + for key, value in substitutions.items(): + line = line.replace(key, value) + + output_file.write(line) + + +def _load_ttl(data_paths, exclude=None): + "Load an RDF model from a Turtle file." + + model = rdflib.ConjunctiveGraph() + for path in data_paths: + if exclude is None or path not in exclude: + model.parse(path, format="n3") + + return model + + +def _warn(message): + "Load a warning message." + + assert not message.startswith("warning: ") + assert not message.endswith("\n") + sys.stderr.write(message) + sys.stderr.write("\n") + + +def _spec_target(spec, root, online=False): + "Return the relative link target for a specification." + + target = spec.removeprefix(root) if spec.startswith(root) else spec + + return target if online else target + ".html" + + +def _spec_date(model, spec, minor, micro): + "Return the date for a release of a specification as an RDF node." + + # Get date + date = None + for release in model.objects(spec, doap.release): + revision = model.value(release, doap.revision, None, any=False) + if str(revision) == f"{minor}.{micro}": + date = model.value(release, doap.created, None) + break + + # Verify that this date is the latest + if date is not None: + for other_release in model.objects(spec, doap.release): + for other_date in model.objects(other_release, doap.created): + if other_date is None: + _warn(f"{spec} has no doap:created date") + elif other_date > date: + _warn(f"{spec} {minor}.{micro} ({date}) is an old release") + break + + return date + + +def _spec_link_columns(spec, root, name, online): + "Return the first two link columns in a spec row as an HTML string." + + # Find relative link target and stem + target = _spec_target(spec, root, online) + stem = os.path.splitext(os.path.basename(target))[0] + + # Prefix with a comment to act as a sort key for the row + col = f"<!-- {stem} -->" + + # Specification + col += f'<td><a rel="rdfs:seeAlso" href="{target}">{name}</a></td>' + + # API + col += '<td><a rel="rdfs:seeAlso"' + col += f' href="../doc/html/group__{stem}.html">{name}' + col += "</a></td>" + + return col + + +def _spec_description_column(model, spec): + "Return the description column in a spec row as an HTML string." + + shortdesc = model.value(spec, doap.shortdesc, None, any=False) + + return "<td>" + str(shortdesc) + "</td>" if shortdesc else "<td></td>" + + +def index_row(model, spec, root_uri, online): + "Return the row for a spec as an HTML string." + + # Get version + minor = 0 + micro = 0 + try: + minor = int(model.value(spec, lv2.minorVersion, None, any=False)) + micro = int(model.value(spec, lv2.microVersion, None, any=False)) + except rdflib.exceptions.UniquenessError: + _warn(f"{spec} has no unique valid version") + return "" + + # Check that date is present and valid + if _spec_date(model, spec, minor, micro) is None: + _warn(f"{spec} has no doap:created date") + return "" + + row = "<tr>" + + # Specification and API + row += _spec_link_columns( + spec, + root_uri, + model.value(spec, doap.name, None).removeprefix("LV2 "), + online, + ) + + # Description + row += _spec_description_column(model, spec) + + # Version + row += f"<td>{minor}.{micro}</td>" + + # Status + deprecated = model.value(spec, owl.deprecated, None) + deprecated = deprecated and str(deprecated) not in ["0", "false"] + if minor == 0: + row += '<td><span class="error">Experimental</span></td>' + elif deprecated: + row += '<td><span class="warning">Deprecated</span></td>' + elif micro % 2 == 0: + row += '<td><span class="success">Stable</span></td>' + else: + row += '<td><span class="warning">Development</span></td>' + + row += "</tr>" + + return row + + +def build_index( + lv2_source_root, + lv2_version, + input_paths, + root_uri, + online, +): + "Build the LV2 specification index and write it to stdout." + + model = _load_ttl(input_paths) + + # Get date for this version, and list of all LV2 distributions + proj = rdflib.URIRef("http://lv2plug.in/ns/lv2") + date = None + for row in model.triples([proj, doap.release, None]): + revision = model.value(row[2], doap.revision, None) + created = model.value(row[2], doap.created, None) + if str(revision) == lv2_version: + date = created + + dist = model.value(row[2], doap["file-release"], None) + if not dist or not created: + _warn(f"{proj} has no file release") + + rows = [] + for spec in model.triples([None, rdf.type, lv2.Specification]): + rows += [index_row(model, spec[0], root_uri, online)] + + if date is None: + now = int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) + date = datetime.datetime.utcfromtimestamp(now).strftime("%F") + + _subst_file( + os.path.join(lv2_source_root, "doc", "index.html.in"), + sys.stdout, + { + "@ROWS@": "\n".join(rows), + "@LV2_VERSION@": lv2_version, + "@DATE@": date, + }, + ) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + usage="%(prog)s [OPTION]... INPUT_PATH...", + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + ap.add_argument("--lv2-version", help="LV2 release version") + ap.add_argument("--lv2-source-root", help="path to LV2 source root") + ap.add_argument( + "--root-uri", + default="http://lv2plug.in/ns/", + help="root URI for specifications", + ) + ap.add_argument( + "--online", + action="store_true", + default=False, + help="build online documentation", + ) + ap.add_argument("input_paths", nargs="+", help="path to Turtle input file") + + args = ap.parse_args(sys.argv[1:]) + + if args.lv2_version is None or args.lv2_source_root is None: + introspect_command = ["meson", "introspect", "-a"] + project_info = json.loads( + subprocess.check_output(introspect_command).decode("utf-8") + ) + + if args.lv2_version is None: + args.lv2_version = project_info["projectinfo"]["version"] + + if args.lv2_source_root is None: + meson_build_path = project_info["buildsystem_files"][0] + args.lv2_source_root = os.path.relpath( + os.path.dirname(meson_build_path) + ) + + build_index(**vars(args)) diff --git a/scripts/lv2_check_specification.py b/scripts/lv2_check_specification.py new file mode 100755 index 0000000..0cd296e --- /dev/null +++ b/scripts/lv2_check_specification.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 + +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: ISC + +""" +Check an LV2 specification for issues. +""" + +import argparse +import os +import sys + +import rdflib + +foaf = rdflib.Namespace("http://xmlns.com/foaf/0.1/") +lv2 = rdflib.Namespace("http://lv2plug.in/ns/lv2core#") +owl = rdflib.Namespace("http://www.w3.org/2002/07/owl#") +rdf = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#") +rdfs = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#") + + +class Checker: + "A callable that checks conditions and records pass/fail counts." + + def __init__(self, verbose=False): + self.num_checks = 0 + self.num_errors = 0 + self.verbose = verbose + + def __call__(self, condition, name): + if not condition: + sys.stderr.write(f"error: Unmet condition: {name}\n") + self.num_errors += 1 + elif self.verbose: + sys.stderr.write(f"note: {name}\n") + + self.num_checks += 1 + return condition + + def print_summary(self): + "Print a summary (if verbose) when all checks are finished." + + if self.verbose: + if self.num_errors: + sys.stderr.write(f"note: Failed {self.num_errors}/") + else: + sys.stderr.write("note: Passed all ") + + sys.stderr.write(f"{self.num_checks} checks\n") + + +def _check(condition, name): + "Check that condition is true, returning 1 on failure." + + if not condition: + sys.stderr.write(f"error: Unmet condition: {name}\n") + return 1 + + return 0 + + +def _has_statement(model, pattern): + "Return true if model contains a triple matching pattern." + + for _ in model.triples(pattern): + return True + + return False + + +def _has_property(model, subject, predicate): + "Return true if subject has any value for predicate in model." + + return model.value(subject, predicate, None) is not None + + +def _check_version(checker, model, spec, is_stable): + "Check that the version of a specification is present and valid." + + minor = model.value(spec, lv2.minorVersion, None, any=False) + checker(minor is not None, f"{spec} has a lv2:minorVersion") + + micro = model.value(spec, lv2.microVersion, None, any=False) + checker(micro is not None, f"{spec} has a lv2:microVersion") + + if is_stable: + checker(int(minor) > 0, f"{spec} has a non-zero minor version") + checker(int(micro) % 2 == 0, f"{spec} has an even micro version") + + +def _check_specification(checker, spec_dir, is_stable=False): + "Check all specification data for errors and omissions." + + # Load manifest + manifest_path = os.path.join(spec_dir, "manifest.ttl") + model = rdflib.Graph() + model.parse(manifest_path, format="n3") + + # Get the specification URI from the manifest + spec_uri = model.value(None, rdf.type, lv2.Specification, any=False) + if not checker( + spec_uri is not None, + manifest_path + " declares an lv2:Specification", + ): + return 1 + + # Check that the manifest declares a valid version + _check_version(checker, model, spec_uri, is_stable) + + # Get the link to the main document from the manifest + document = model.value(spec_uri, rdfs.seeAlso, None, any=False) + if not checker( + document is not None, + manifest_path + " has one rdfs:seeAlso link to the definition", + ): + return 1 + + # Load main document into the model + model.parse(document, format="n3") + + # Check that the main data files aren't bloated with extended documentation + checker( + not _has_statement(model, [None, lv2.documentation, None]), + f"{document} has no lv2:documentation", + ) + + # Load all other directly linked data files (for any other subjects) + for link in sorted(model.triples([None, rdfs.seeAlso, None])): + if link[2] != document and link[2].endswith(".ttl"): + model.parse(link[2], format="n3") + + # Check that all properties have a more specific type + for typing in sorted(model.triples([None, rdf.type, rdf.Property])): + subject = typing[0] + + checker(isinstance(subject, rdflib.term.URIRef), f"{subject} is a URI") + + if str(subject) == "http://lv2plug.in/ns/ext/patch#value": + continue # patch:value is just a "promiscuous" rdf:Property + + types = list(model.objects(subject, rdf.type)) + + checker( + (owl.DatatypeProperty in types) + or (owl.ObjectProperty in types) + or (owl.AnnotationProperty in types), + f"{subject} is a Datatype, Object, or Annotation property", + ) + + # Get all subjects that have an explicit rdf:type + typed_subjects = set() + for typing in model.triples([None, rdf.type, None]): + typed_subjects.add(typing[0]) + + # Check that all named and typed resources have labels and comments + for subject in typed_subjects: + if isinstance( + subject, rdflib.term.BNode + ) or foaf.Person in model.objects(subject, rdf.type): + continue + + if checker( + _has_property(model, subject, rdfs.label), + f"{subject} has a rdfs:label", + ): + label = str(model.value(subject, rdfs.label, None)) + + checker( + not label.endswith("."), + f"{subject} label has no trailing '.'", + ) + checker( + label.find("\n") == -1, + f"{subject} label is a single line", + ) + checker( + label == label.strip(), + f"{subject} label has stripped whitespace", + ) + + if checker( + _has_property(model, subject, rdfs.comment), + f"{subject} has a rdfs:comment", + ): + comment = str(model.value(subject, rdfs.comment, None)) + + checker( + comment.endswith("."), + f"{subject} comment has a trailing '.'", + ) + checker( + comment.find("\n") == -1 and comment.find("\r"), + f"{subject} comment is a single line", + ) + checker( + comment == comment.strip(), + f"{subject} comment has stripped whitespace", + ) + + # Check that lv2:documentation, if present, is proper Markdown + documentation = model.value(subject, lv2.documentation, None) + if documentation is not None: + checker( + documentation.datatype == lv2.Markdown, + f"{subject} documentation is explicitly Markdown", + ) + checker( + str(documentation).startswith("\n\n"), + f"{subject} documentation starts with blank line", + ) + checker( + str(documentation).endswith("\n\n"), + f"{subject} documentation ends with blank line", + ) + + return checker.num_errors + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + usage="%(prog)s [OPTION]... BUNDLE", + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + ap.add_argument( + "--stable", + action="store_true", + help="enable checks for stable release versions", + ) + + ap.add_argument( + "-v", "--verbose", action="store_true", help="print successful checks" + ) + + ap.add_argument( + "BUNDLE", help="path to specification bundle or manifest.ttl" + ) + + args = ap.parse_args(sys.argv[1:]) + + if os.path.basename(args.BUNDLE): + args.BUNDLE = os.path.dirname(args.BUNDLE) + + sys.exit( + _check_specification(Checker(args.verbose), args.BUNDLE, args.stable) + ) diff --git a/scripts/lv2_check_syntax.py b/scripts/lv2_check_syntax.py new file mode 100755 index 0000000..d1b72dc --- /dev/null +++ b/scripts/lv2_check_syntax.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: ISC + +""" +Check that a Turtle file has valid syntax and strict formatting. + +This is a strict tool that enforces machine formatting with serdi. +""" + +import argparse +import difflib +import filecmp +import sys +import tempfile +import os +import subprocess + + +def _show_diff(from_lines, to_lines, from_path, to_path): + "Show a diff between two files, returning non-zero if they differ." + + differences = False + for line in difflib.unified_diff( + from_lines, + to_lines, + fromfile=from_path, + tofile=to_path, + ): + sys.stderr.write(line) + differences = True + + return int(differences) + + +def _check_file_equals(patha, pathb): + "Check that two files are equal, returning non-zero if they differ." + + for path in (patha, pathb): + if not os.access(path, os.F_OK): + sys.stderr.write(f"error: missing file {path}") + return 1 + + if filecmp.cmp(patha, pathb, shallow=False): + return 0 + + with open(patha, "r", encoding="utf-8") as in_a: + with open(pathb, "r", encoding="utf-8") as in_b: + return _show_diff(in_a.readlines(), in_b.readlines(), patha, pathb) + + +def run(serdi, filenames): + "Check that every file in filenames has valid formatted syntax." + + status = 0 + + for filename in filenames: + rel_path = os.path.relpath(filename) + with tempfile.NamedTemporaryFile(mode="w") as out: + command = [serdi, "-o", "turtle", rel_path] + subprocess.check_call(command, stdout=out) + + if _check_file_equals(rel_path, out.name): + status = 1 + + return status + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + usage="%(prog)s [OPTION]... TURTLE_FILE...", + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + ap.add_argument("--serdi", default="serdi", help="path to serdi") + ap.add_argument("TURTLE_FILE", nargs="+", help="input file to check") + + args = ap.parse_args(sys.argv[1:]) + + sys.exit(run(args.serdi, args.TURTLE_FILE)) diff --git a/scripts/lv2_write_news.py b/scripts/lv2_write_news.py new file mode 100755 index 0000000..6ce935c --- /dev/null +++ b/scripts/lv2_write_news.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 + +# Copyright 2020-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: ISC + +""" +Write a NEWS file from RDF data. + +The output is in Debian changelog format, which can be parsed by +dpkg-parsechangelog, among other things. +""" + +import argparse +import os +import sys +import datetime +import textwrap +import urllib +import re + +import rdflib + +doap = rdflib.Namespace("http://usefulinc.com/ns/doap#") +dcs = rdflib.Namespace("http://ontologi.es/doap-changeset#") +rdfs = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#") +foaf = rdflib.Namespace("http://xmlns.com/foaf/0.1/") +rdf = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#") + + +def _is_release_version(version): + "Return true if `version` is a stable version number." + + if len(version) not in [2, 3] or version[0] == 0: + return False + + minor = version[len(version) - 2] + micro = version[len(version) - 1] + + return micro % 2 == 0 and (len(version) == 2 or minor % 2 == 0) + + +def _parse_datetime(string): + "Parse string as either a datetime or a date." + + try: + return datetime.datetime.strptime(string, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + return datetime.datetime.strptime(string, "%Y-%m-%d") + + +def _release_entry(graph, release): + "Return a news entry for a release." + + revision = graph.value(release, doap.revision, None) + date = graph.value(release, doap.created, None) + blamee = graph.value(release, dcs.blame, None) + changeset = graph.value(release, dcs.changeset, None) + dist = graph.value(release, doap["file-release"], None) + + if not revision or not date or not blamee or not changeset: + return None + + version = tuple(map(int, revision.split("."))) + + entry = { + "version": version, + "revision": str(revision), + "date": _parse_datetime(date), + "status": "stable" if _is_release_version(version) else "unstable", + "items": [], + } + + if dist is not None: + entry["dist"] = dist + + for j in graph.triples([changeset, dcs.item, None]): + item = str(graph.value(j[2], rdfs.label, None)) + entry["items"] += [item] + + entry["blamee_name"] = str(graph.value(blamee, foaf.name, None)) + entry["blamee_mbox"] = str(graph.value(blamee, foaf.mbox, None)) + return entry + + +def _project_entries(graph, project): + "Return a map from version to news entries for a project" + + entries = {} + for link in graph.triples([project, doap.release, None]): + entry = _release_entry(graph, link[2]) + if entry is not None: + entries[entry["version"]] = entry + else: + sys.stderr.write(f"warning: Ignored partial {project} release\n") + + return entries + + +def _read_turtle_news(in_files): + "Read news entries from Turtle." + + graph = rdflib.Graph() + + # Parse input files + for i in in_files: + graph.parse(i) + + # Read news for every project in the data + projects = {t[0] for t in graph.triples([None, rdf.type, doap.Project])} + entries_by_project = {} + for project in projects: + # Load any associated files + for uri in graph.triples([project, rdfs.seeAlso, None]): + if uri[2].endswith(".ttl"): + graph.parse(uri[2]) + + # Use the symbol from the URI as a name, or failing that, the doap:name + name = os.path.basename(urllib.parse.urlparse(str(project)).path) + if not name: + name = graph.value(project, doap.name, None) + + entries = _project_entries(graph, project) + for _, entry in entries.items(): + entry["name"] = name + + entries_by_project[str(project)] = entries + + return entries_by_project + + +def _write_news_item(out, item): + "Write a single item (change) in NEWS format." + + out.write("\n * " + "\n ".join(textwrap.wrap(item, width=74))) + + +def _write_news_entry(out, entry): + "Write an entry (version) to out in NEWS format." + + # Summary header + summary = f'{entry["name"]} ({entry["revision"]}) {entry["status"]}' + out.write(f"{summary}; urgency=medium\n") + + # Individual change items + for item in sorted(entry["items"]): + _write_news_item(out, item) + + # Trailer line + mbox = entry["blamee_mbox"].replace("mailto:", "") + author = f'{entry["blamee_name"]} <{mbox}>' + date = entry["date"] + if date.tzinfo is None: # Assume UTC (dpkg-parsechangelog requires it) + date = date.strftime("%a, %d %b %Y %H:%M:%S +0000") + else: + date = date.strftime("%a, %d %b %Y %H:%M:%S %z") + + out.write(f"\n\n -- {author} {date}\n") + + +def _write_single_project_news(out, entries): + "Write a NEWS file for entries of a single project to out." + + revisions = sorted(entries.keys(), reverse=True) + for revision in revisions: + entry = entries[revision] + out.write("\n" if revision != revisions[0] else "") + _write_news_entry(out, entry) + + +def _write_meta_project_news(out, top_project, entries_by_project): + "Write a NEWS file for a meta-project that contains others." + + top_name = os.path.basename(urllib.parse.urlparse(str(top_project)).path) + release_pattern = rf".*/{top_name}-([0-9\.]*).tar.bz2" + + # Pop the entries for the top project + top_entries = entries_by_project.pop(top_project) + + # Add items from the other projects to the corresponding top entry + for _, entries in entries_by_project.items(): + for version, entry in entries.items(): + if "dist" in entry: + match = re.match(release_pattern, entry["dist"]) + if match: + version = tuple(map(int, match.group(1).split("."))) + for item in entry["items"]: + top_entries[version]["items"] += [ + f'{entry["name"]}: {item}' + ] + + for version in sorted(top_entries.keys(), reverse=True): + out.write("\n" if version != max(top_entries.keys()) else "") + _write_news_entry(out, top_entries[version]) + + +def _write_text_news(out, entries_by_project, top_project=None): + "Write NEWS in standard Debian changelog format." + + if len(entries_by_project) > 1: + if top_project is None: + sys.stderr.write("error: --top is required for multi-projects\n") + return 1 + + _write_meta_project_news(out, top_project, entries_by_project) + else: + project = next(iter(entries_by_project)) + _write_single_project_news(out, entries_by_project[project]) + + return 0 + + +if __name__ == "__main__": + ap = argparse.ArgumentParser( + usage="%(prog)s [OPTION]... DATA_FILE...", + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + ap.add_argument( + "-o", + "--output", + metavar="OUTPUT_FILE", + help="output file path", + ) + + ap.add_argument( + "-t", + "--top-project", + metavar="OUTPUT_FILE", + help="URI of parent meta-project with file releases", + ) + + ap.add_argument( + "DATA_FILE", + nargs="+", + help="path to a Turtle file with release data", + ) + + args = ap.parse_args(sys.argv[1:]) + + if not args.output and "MESON_DIST_ROOT" in os.environ: + args.output = os.path.join(os.getenv("MESON_DIST_ROOT"), "NEWS") + + if not args.output: + sys.exit( + _write_text_news( + sys.stdout, _read_turtle_news(args.DATA_FILE), args.top_project + ) + ) + else: + with open(args.output, "w", encoding="utf-8") as output_file: + sys.exit( + _write_text_news( + output_file, + _read_turtle_news(args.DATA_FILE), + args.top_project, + ) + ) diff --git a/scripts/meson.build b/scripts/meson.build new file mode 100644 index 0000000..400d583 --- /dev/null +++ b/scripts/meson.build @@ -0,0 +1,9 @@ +# Copyright 2021-2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +lv2_scripts = files( + 'lv2_build_index.py', + 'lv2_check_specification.py', + 'lv2_check_syntax.py', + 'lv2_write_news.py', +) diff --git a/test/.clang-tidy b/test/.clang-tidy new file mode 100644 index 0000000..7846913 --- /dev/null +++ b/test/.clang-tidy @@ -0,0 +1,15 @@ +Checks: > + *, + -*-magic-numbers, + -*-uppercase-literal-suffix, + -altera-*, + -bugprone-easily-swappable-parameters, + -bugprone-macro-parentheses, + -llvm-header-guard, + -llvmlibc-implementation-in-namespace, + -llvmlibc-restrict-system-libc-headers, + -modernize-use-trailing-return-type, + -performance-no-int-to-ptr, +WarningsAsErrors: '*' +HeaderFilterRegex: '.*' +FormatStyle: file diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..0b8339a --- /dev/null +++ b/test/meson.build @@ -0,0 +1,110 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +######## +# Data # +######## + +# Check for spelling errors +codespell = find_program('codespell', required: get_option('tests')) +if codespell.found() + ignore = [ + lv2_source_root / 'doc' / 'pygments.css', + lv2_source_root / 'lv2specgen' / 'DTD', + lv2_source_root / 'schemas.lv2' / 'doap.ttl', + ] + + test( + 'codespell', + codespell, + args: [ + '-d', + '-q', '3', + '-S', ','.join(ignore), + lv2_source_root / 'doc', + lv2_source_root / 'lv2', + lv2_source_root / 'lv2specgen', + lv2_source_root / 'plugins', + lv2_source_root / 'schemas.lv2', + ], + suite: 'data', + ) +endif + +# Check that specification data is strictly formatted +serdi = find_program('serdi', required: get_option('tests')) +if serdi.found() + lv2_check_syntax = files(lv2_source_root / 'scripts' / 'lv2_check_syntax.py') + + test('syntax', + lv2_check_syntax, + args: ['--serdi', serdi.full_path()] + spec_files + schema_data, + suite: 'data') +endif + +# Check that specification data validates +sord_validate = find_program('sord_validate', required: get_option('tests')) +if sord_validate.found() + test('valid', + sord_validate, + args: spec_files + schema_data, + suite: 'data') +endif + +######## +# Code # +######## + +# Check that all the headers compile cleanly in C +test('c', + executable( + 'test_build_c', + files('test_build.c'), + c_args: c_suppressions, + dependencies: lv2_dep, + ), + suite: 'build') + +# Check that all the headers compile cleanly in C++ +if is_variable('cpp') + test('cpp', + executable( + 'test_build_cpp', + files('test_build.cpp'), + cpp_args: cpp_suppressions, + dependencies: lv2_dep, + ), + suite: 'build') +endif + +########## +# Python # +########## + +flake8 = find_program('flake8', required: get_option('tests')) +pylint = find_program('pylint', required: get_option('tests')) +black = find_program('black', required: get_option('tests')) + +# Scripts that don't pass with pylint +lax_python_scripts = files( + '../lv2specgen/lv2docgen.py', + '../lv2specgen/lv2specgen.py', +) + +# Scripts that pass with everything including pylint +strict_python_scripts = lv2_scripts + files('../plugins/literasc.py') + +all_python_scripts = lax_python_scripts + strict_python_scripts + +if is_variable('black') and black.found() + black_opts = ['-l', '79', '-q', '--check'] + test('black', black, args: black_opts + all_python_scripts, suite: 'scripts') +endif + +if is_variable('flake8') and flake8.found() + test('flake8', flake8, args: all_python_scripts, suite: 'scripts') +endif + +if is_variable('pylint') and pylint.found() + test('pylint', pylint, args: strict_python_scripts, suite: 'scripts') +endif diff --git a/test/test_build.c b/test/test_build.c new file mode 100644 index 0000000..146ad71 --- /dev/null +++ b/test/test_build.c @@ -0,0 +1,52 @@ +/* + Copyright 2022 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "lv2/atom/atom.h" // IWYU pragma: keep +#include "lv2/atom/forge.h" // IWYU pragma: keep +#include "lv2/atom/util.h" // IWYU pragma: keep +#include "lv2/buf-size/buf-size.h" // IWYU pragma: keep +#include "lv2/core/attributes.h" // IWYU pragma: keep +#include "lv2/core/lv2.h" // IWYU pragma: keep +#include "lv2/core/lv2_util.h" // IWYU pragma: keep +#include "lv2/data-access/data-access.h" // IWYU pragma: keep +#include "lv2/dynmanifest/dynmanifest.h" // IWYU pragma: keep +#include "lv2/event/event-helpers.h" // IWYU pragma: keep +#include "lv2/event/event.h" // IWYU pragma: keep +#include "lv2/instance-access/instance-access.h" // IWYU pragma: keep +#include "lv2/log/log.h" // IWYU pragma: keep +#include "lv2/log/logger.h" // IWYU pragma: keep +#include "lv2/midi/midi.h" // IWYU pragma: keep +#include "lv2/morph/morph.h" // IWYU pragma: keep +#include "lv2/options/options.h" // IWYU pragma: keep +#include "lv2/parameters/parameters.h" // IWYU pragma: keep +#include "lv2/patch/patch.h" // IWYU pragma: keep +#include "lv2/port-groups/port-groups.h" // IWYU pragma: keep +#include "lv2/port-props/port-props.h" // IWYU pragma: keep +#include "lv2/presets/presets.h" // IWYU pragma: keep +#include "lv2/resize-port/resize-port.h" // IWYU pragma: keep +#include "lv2/state/state.h" // IWYU pragma: keep +#include "lv2/time/time.h" // IWYU pragma: keep +#include "lv2/ui/ui.h" // IWYU pragma: keep +#include "lv2/units/units.h" // IWYU pragma: keep +#include "lv2/uri-map/uri-map.h" // IWYU pragma: keep +#include "lv2/urid/urid.h" // IWYU pragma: keep +#include "lv2/worker/worker.h" // IWYU pragma: keep + +int +main(void) +{ + return 0; +} diff --git a/test/test_build.cpp b/test/test_build.cpp new file mode 100644 index 0000000..dc269a6 --- /dev/null +++ b/test/test_build.cpp @@ -0,0 +1,67 @@ +/* + Copyright 2022 David Robillard <d@drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#if defined(__clang__) +_Pragma("clang diagnostic push") +_Pragma("clang diagnostic ignored \"-Wold-style-cast\"") +_Pragma("clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"") +#elif defined(__GNUC__) +_Pragma("GCC diagnostic push") +_Pragma("GCC diagnostic ignored \"-Wsuggest-attribute=const\"") +#endif + +#include "lv2/atom/atom.h" // IWYU pragma: keep +#include "lv2/atom/forge.h" // IWYU pragma: keep +#include "lv2/atom/util.h" // IWYU pragma: keep +#include "lv2/buf-size/buf-size.h" // IWYU pragma: keep +#include "lv2/core/attributes.h" // IWYU pragma: keep +#include "lv2/core/lv2.h" // IWYU pragma: keep +#include "lv2/core/lv2_util.h" // IWYU pragma: keep +#include "lv2/data-access/data-access.h" // IWYU pragma: keep +#include "lv2/dynmanifest/dynmanifest.h" // IWYU pragma: keep +#include "lv2/event/event-helpers.h" // IWYU pragma: keep +#include "lv2/event/event.h" // IWYU pragma: keep +#include "lv2/instance-access/instance-access.h" // IWYU pragma: keep +#include "lv2/log/log.h" // IWYU pragma: keep +#include "lv2/log/logger.h" // IWYU pragma: keep +#include "lv2/midi/midi.h" // IWYU pragma: keep +#include "lv2/morph/morph.h" // IWYU pragma: keep +#include "lv2/options/options.h" // IWYU pragma: keep +#include "lv2/parameters/parameters.h" // IWYU pragma: keep +#include "lv2/patch/patch.h" // IWYU pragma: keep +#include "lv2/port-groups/port-groups.h" // IWYU pragma: keep +#include "lv2/port-props/port-props.h" // IWYU pragma: keep +#include "lv2/presets/presets.h" // IWYU pragma: keep +#include "lv2/resize-port/resize-port.h" // IWYU pragma: keep +#include "lv2/state/state.h" // IWYU pragma: keep +#include "lv2/time/time.h" // IWYU pragma: keep +#include "lv2/ui/ui.h" // IWYU pragma: keep +#include "lv2/units/units.h" // IWYU pragma: keep +#include "lv2/uri-map/uri-map.h" // IWYU pragma: keep +#include "lv2/urid/urid.h" // IWYU pragma: keep +#include "lv2/worker/worker.h" // IWYU pragma: keep + +int +main() +{ + return 0; +} + +#if defined(__clang__) +_Pragma("clang diagnostic pop") +#elif defined(__GNUC__) +_Pragma("GCC diagnostic pop") +#endif diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..ed43f1e --- /dev/null +++ b/util/meson.build @@ -0,0 +1,12 @@ +# Copyright 2022 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: CC0-1.0 OR ISC + +config = configuration_data({'LV2DIR': lv2dir}) + +lv2_validate = configure_file( + configuration: config, + input: files('lv2_validate.in'), + install_dir: get_option('bindir'), + install_mode: 'rwxr-xr-x', + output: 'lv2_validate', +) @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -# Minimal waf script for projects that include waflib directly - -import sys -import inspect -import os - -try: - from waflib import Context, Scripting -except Exception as e: - sys.stderr.write('error: Failed to import waf (%s)\n' % e) - if os.path.exists('.git'): - sys.stderr.write("Are submodules up to date? " - "Try 'git submodule update --init --recursive'\n") - - sys.exit(1) - - -def main(): - script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) - project_path = os.path.dirname(os.path.realpath(script_path)) - Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) - - -if __name__ == '__main__': - main() diff --git a/waflib b/waflib deleted file mode 160000 -Subproject b600c928b221a001faeab7bd92786d0b25714bc diff --git a/wscript b/wscript deleted file mode 100644 index 43206ec..0000000 --- a/wscript +++ /dev/null @@ -1,850 +0,0 @@ -#!/usr/bin/env python - -import os -import re -import sys - -from waflib import Build, Context, Logs, Options, Scripting, Utils -from waflib.extras import autowaf as autowaf - -# Mandatory waf variables -APPNAME = 'lv2' # Package name for waf dist -VERSION = '1.18.5' # Package version for waf dist -top = '.' # Source directory -out = 'build' # Build directory - -# Release variables -title = 'LV2' -uri = 'http://lv2plug.in/ns/lv2' -dist_pattern = 'http://lv2plug.in/spec/lv2-%d.%d.%d.tar.bz2' -post_tags = [] - -# Links for documentation -list_email = 'devel@lists.lv2plug.in' -list_page = 'http://lists.lv2plug.in/listinfo.cgi/devel-lv2plug.in' - -# Map of specification base name to old URI-style include path -spec_map = { - 'atom': 'lv2/lv2plug.in/ns/ext/atom', - 'buf-size': 'lv2/lv2plug.in/ns/ext/buf-size', - 'core': 'lv2/lv2plug.in/ns/lv2core', - 'data-access': 'lv2/lv2plug.in/ns/ext/data-access', - 'dynmanifest': 'lv2/lv2plug.in/ns/ext/dynmanifest', - 'event': 'lv2/lv2plug.in/ns/ext/event', - 'instance-access': 'lv2/lv2plug.in/ns/ext/instance-access', - 'log': 'lv2/lv2plug.in/ns/ext/log', - 'midi': 'lv2/lv2plug.in/ns/ext/midi', - 'morph': 'lv2/lv2plug.in/ns/ext/morph', - 'options': 'lv2/lv2plug.in/ns/ext/options', - 'parameters': 'lv2/lv2plug.in/ns/ext/parameters', - 'patch': 'lv2/lv2plug.in/ns/ext/patch', - 'port-groups': 'lv2/lv2plug.in/ns/ext/port-groups', - 'port-props': 'lv2/lv2plug.in/ns/ext/port-props', - 'presets': 'lv2/lv2plug.in/ns/ext/presets', - 'resize-port': 'lv2/lv2plug.in/ns/ext/resize-port', - 'state': 'lv2/lv2plug.in/ns/ext/state', - 'time': 'lv2/lv2plug.in/ns/ext/time', - 'ui': 'lv2/lv2plug.in/ns/extensions/ui', - 'units': 'lv2/lv2plug.in/ns/extensions/units', - 'uri-map': 'lv2/lv2plug.in/ns/ext/uri-map', - 'urid': 'lv2/lv2plug.in/ns/ext/urid', - 'worker': 'lv2/lv2plug.in/ns/ext/worker'} - - -def options(ctx): - ctx.load('compiler_c') - ctx.load('compiler_cxx') - ctx.load('lv2') - ctx.add_flags( - ctx.configuration_options(), - {'no-coverage': 'Do not use gcov for code coverage', - 'online-docs': 'Build documentation for web hosting', - 'no-check-links': 'Do not check documentation for broken links', - 'no-plugins': 'Do not build example plugins', - 'copy-headers': 'Copy headers instead of linking to bundle'}) - - -def configure(conf): - try: - conf.load('compiler_c', cache=True) - except Exception: - Options.options.build_tests = False - Options.options.no_plugins = True - - try: - conf.load('compiler_cxx', cache=True) - except Exception: - pass - - if Options.options.online_docs: - Options.options.docs = True - - conf.load('lv2', cache=True) - conf.load('autowaf', cache=True) - - if Options.options.strict: - # Check for programs used by lint target - conf.find_program("flake8", var="FLAKE8", mandatory=False) - conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False) - conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False) - - if Options.options.ultra_strict: - autowaf.add_compiler_flags(conf.env, 'c', { - 'gcc': [ - '-Wno-bad-function-cast', - ], - 'clang': [ - '-Wno-bad-function-cast', - ] - }) - - autowaf.add_compiler_flags(conf.env, '*', { - 'clang': [ - '-Wno-cast-align', - '-Wno-cast-qual', - '-Wno-documentation-unknown-command', - '-Wno-double-promotion', - '-Wno-float-conversion', - '-Wno-float-equal', - '-Wno-implicit-float-conversion', - '-Wno-padded', - '-Wno-poison-system-directories', - '-Wno-reserved-id-macro', - '-Wno-shorten-64-to-32', - '-Wno-sign-conversion', - '-Wno-switch-enum', - '-Wno-unused-parameter', - ], - 'gcc': [ - '-Wno-cast-align', - '-Wno-cast-qual', - '-Wno-conversion', - '-Wno-double-promotion', - '-Wno-float-equal', - '-Wno-inline', - '-Wno-padded', - '-Wno-suggest-attribute=const', - '-Wno-suggest-attribute=malloc', - '-Wno-suggest-attribute=pure', - '-Wno-switch-enum', - '-Wno-unused-parameter', - ], - 'msvc': [ - '/wd4061', # enumerator in switch is not explicitly handled - '/wd4100', # unreferenced formal parameter - '/wd4244', # conversion with possible loss of data - '/wd4267', # conversion from size_t to a smaller type - '/wd4310', # cast truncates constant value - '/wd4365', # signed/unsigned mismatch - '/wd4464', # relative include path contains ".." - '/wd4514', # unreferenced inline function has been removed - '/wd4706', # assignment within conditional expression - '/wd4710', # function not inlined - '/wd4711', # function selected for automatic inline expansion - '/wd4820', # padding added after construct - '/wd5045', # will insert Spectre mitigation for memory load - ] - }) - - autowaf.add_compiler_flags(conf.env, 'cxx', { - 'gcc': [ - '-Wno-useless-cast', - '-Wno-zero-as-null-pointer-constant', - ], - 'clang': [ - '-Wno-old-style-cast', - '-Wno-zero-as-null-pointer-constant', - ] - }) - - if 'mingw' in conf.env.CC[0]: - autowaf.add_compiler_flags(conf.env, '*', { - 'gcc': [ - '-Wno-format', - '-Wno-suggest-attribute=format', - ], - }) - - autowaf.set_c_lang(conf, 'c99') - - if conf.env.DEST_OS == 'win32' or not hasattr(os.path, 'relpath'): - Logs.warn('System does not support linking headers, copying') - Options.options.copy_headers = True - - conf.env.BUILD_TESTS = Options.options.build_tests - conf.env.BUILD_PLUGINS = not Options.options.no_plugins - conf.env.COPY_HEADERS = Options.options.copy_headers - conf.env.ONLINE_DOCS = Options.options.online_docs - - if conf.env.DOCS or conf.env.ONLINE_DOCS: - try: - conf.find_program('asciidoc') - conf.env.BUILD_BOOK = True - except Exception: - Logs.warn('Asciidoc not found, book will not be built') - - if not Options.options.no_check_links: - if not conf.find_program('linkchecker', - var='LINKCHECKER', mandatory=False): - Logs.warn('Documentation will not be checked for broken links') - - # Check for gcov library (for test coverage) - if (conf.env.BUILD_TESTS and - not Options.options.no_coverage and - not conf.is_defined('HAVE_GCOV')): - conf.check_cc(lib='gcov', define_name='HAVE_GCOV', mandatory=False) - - if conf.env.BUILD_TESTS: - conf.find_program('serdi', mandatory=False) - conf.find_program('sord_validate', mandatory=False) - conf.find_program('codespell', mandatory=False) - - autowaf.set_lib_env(conf, 'lv2', VERSION, has_objects=False) - autowaf.set_local_lib(conf, 'lv2', has_objects=False) - - conf.run_env.append_unique('LV2_PATH', - [os.path.join(conf.path.abspath(), 'lv2')]) - - if conf.env.BUILD_PLUGINS: - for i in ['eg-amp.lv2', - 'eg-fifths.lv2', - 'eg-metro.lv2', - 'eg-midigate.lv2', - 'eg-params.lv2', - 'eg-sampler.lv2', - 'eg-scope.lv2']: - try: - path = os.path.join('plugins', i) - conf.recurse(path) - conf.env.LV2_BUILD += [path] - conf.run_env.append_unique( - 'LV2_PATH', [conf.build_path('plugins/%s/lv2' % i)]) - except Exception as e: - Logs.warn('Configuration of %s failed (%s)' % (i, e)) - - autowaf.display_summary( - conf, - {'Bundle directory': conf.env.LV2DIR, - 'Copy (not link) headers': bool(conf.env.COPY_HEADERS), - 'Version': VERSION}) - - -def chop_lv2_prefix(s): - if s.startswith('lv2/lv2plug.in/'): - return s[len('lv2/lv2plug.in/'):] - return s - - -def subst_file(template, output, dict): - i = open(template, 'r') - o = open(output, 'w') - for line in i: - for key in dict: - line = line.replace(key, dict[key]) - o.write(line) - i.close() - o.close() - - -def specdirs(path): - return (path.ant_glob('lv2/*', dir=True) + - path.ant_glob('plugins/*.lv2', dir=True)) - - -def ttl_files(path, specdir): - def abspath(node): - return node.abspath() - - return map(abspath, - path.ant_glob(specdir.path_from(path) + '/*.ttl')) - - -def load_ttl(files, exclude = []): - import rdflib - model = rdflib.ConjunctiveGraph() - for f in files: - if f not in exclude: - model.parse(f, format='n3') - return model - - -# Task to build extension index -def build_index(task): - src_dir = task.inputs[0].parent.parent - sys.path.append(str(src_dir.find_node('lv2specgen'))) - import rdflib - - doap = rdflib.Namespace('http://usefulinc.com/ns/doap#') - - model = load_ttl([str(src_dir.find_node('lv2/core/meta.ttl')), - str(src_dir.find_node('lv2/core/people.ttl'))]) - - # Get date for this version, and list of all LV2 distributions - proj = rdflib.URIRef('http://lv2plug.in/ns/lv2') - date = None - dists = [] - for r in model.triples([proj, doap.release, None]): - revision = model.value(r[2], doap.revision, None) - created = model.value(r[2], doap.created, None) - if str(revision) == VERSION: - date = created - - dist = model.value(r[2], doap['file-release'], None) - if dist and created: - dists += [(created, dist)] - else: - print('warning: %s has no file release\n' % proj) - - rows = [] - for f in task.inputs: - if not f.abspath().endswith('index.html.in'): - rowfile = open(f.abspath(), 'r') - rows += rowfile.readlines() - rowfile.close() - - if date is None: - import datetime - import time - now = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) - date = datetime.datetime.utcfromtimestamp(now).strftime('%F') - - subst_file(task.inputs[0].abspath(), task.outputs[0].abspath(), - {'@ROWS@': ''.join(rows), - '@LV2_VERSION@': VERSION, - '@DATE@': date}) - - -def build_spec(bld, path): - name = os.path.basename(path) - bundle_dir = os.path.join(bld.env.LV2DIR, name + '.lv2') - include_dir = os.path.join(bld.env.INCLUDEDIR, path) - old_include_dir = os.path.join(bld.env.INCLUDEDIR, spec_map[name]) - - # Build test program if applicable - for test in bld.path.ant_glob(os.path.join(path, '*-test.c')): - test_lib = [] - test_cflags = [''] - test_linkflags = [''] - if bld.is_defined('HAVE_GCOV'): - test_lib += ['gcov'] - test_cflags += ['--coverage'] - test_linkflags += ['--coverage'] - if bld.env.DEST_OS not in ['darwin', 'win32']: - test_lib += ['rt'] - - # Unit test program - bld(features = 'c cprogram', - source = test, - lib = test_lib, - uselib = 'LV2', - target = os.path.splitext(str(test.get_bld()))[0], - install_path = None, - cflags = test_cflags, - linkflags = test_linkflags) - - # Install bundle - bld.install_files(bundle_dir, - bld.path.ant_glob(path + '/?*.*', excl='*.in')) - - # Install URI-like includes - headers = bld.path.ant_glob(path + '/*.h') - if headers: - for d in [include_dir, old_include_dir]: - if bld.env.COPY_HEADERS: - bld.install_files(d, headers) - else: - bld.symlink_as(d, - os.path.relpath(bundle_dir, os.path.dirname(d))) - - -def build(bld): - specs = (bld.path.ant_glob('lv2/*', dir=True)) - - # Copy lv2.h to include directory for backwards compatibility - old_lv2_h_path = os.path.join(bld.env.INCLUDEDIR, 'lv2.h') - if bld.env.COPY_HEADERS: - bld.install_files(os.path.dirname(old_lv2_h_path), 'lv2/core/lv2.h') - else: - bld.symlink_as(old_lv2_h_path, 'lv2/core/lv2.h') - - # LV2 pkgconfig file - bld(features = 'subst', - source = 'lv2.pc.in', - target = 'lv2.pc', - install_path = '${LIBDIR}/pkgconfig', - PREFIX = bld.env.PREFIX, - INCLUDEDIR = bld.env.INCLUDEDIR, - VERSION = VERSION) - - # Validator - bld(features = 'subst', - source = 'util/lv2_validate.in', - target = 'lv2_validate', - chmod = Utils.O755, - install_path = '${BINDIR}', - LV2DIR = bld.env.LV2DIR) - - # Build extensions - for spec in specs: - build_spec(bld, spec.path_from(bld.path)) - - # Build plugins - for plugin in bld.env.LV2_BUILD: - bld.recurse(plugin) - - # Install lv2specgen - bld.install_files('${DATADIR}/lv2specgen/', - ['doc/style.css', - 'lv2specgen/template.html']) - bld.install_files('${DATADIR}/lv2specgen/DTD/', - bld.path.ant_glob('lv2specgen/DTD/*')) - bld.install_files('${BINDIR}', 'lv2specgen/lv2specgen.py', - chmod=Utils.O755) - - # Install schema bundle - bld.install_files('${LV2DIR}/schemas.lv2/', - bld.path.ant_glob('schemas.lv2/*.ttl')) - - if bld.env.ONLINE_DOCS: - # Generate .htaccess files - for d in ('ns', 'ns/ext', 'ns/extensions'): - path = os.path.join(str(bld.path.get_bld()), d) - bld(features = 'subst', - source = 'doc/htaccess.in', - target = os.path.join(path, '.htaccess'), - install_path = None, - BASE = '/' + d) - - if bld.env.DOCS or bld.env.ONLINE_DOCS: - # Copy spec files to build dir - for spec in specs: - srcpath = spec.path_from(bld.path) - basename = os.path.basename(srcpath) - full_path = spec_map[basename] - name = 'lv2core' if basename == 'core' else basename - path = chop_lv2_prefix(full_path) - - bld(features = 'subst', - is_copy = True, - source = os.path.join(srcpath, name + '.ttl'), - target = path + '.ttl') - - # Copy stylesheets to build directory - for i in ['style.css', 'pygments.css']: - bld(features = 'subst', - is_copy = True, - name = 'copy', - source = 'doc/%s' % i, - target = 'aux/%s' % i) - - # Build Doxygen documentation (and tags file) - autowaf.build_dox(bld, 'LV2', VERSION, top, out, 'doc', False) - bld.add_group() - - index_files = [] - for spec in specs: - # Call lv2specgen to generate spec docs - srcpath = spec.path_from(bld.path) - basename = os.path.basename(srcpath) - full_path = spec_map[basename] - name = 'lv2core' if basename == 'core' else basename - ttl_name = name + '.ttl' - index_file = bld.path.get_bld().make_node('index_rows/' + name) - index_files += [index_file] - chopped_path = chop_lv2_prefix(full_path) - - assert chopped_path.startswith('ns/') - root_path = os.path.relpath('/', os.path.dirname(chopped_path[2:])) - html_path = '%s.html' % chopped_path - out_dir = os.path.dirname(html_path) - style_uri = os.path.relpath('aux/style.css', out_dir) - - cmd = (str(bld.path.find_node('lv2specgen/lv2specgen.py')) + - ' --root-uri=http://lv2plug.in/ns/' - ' --root-path=' + root_path + - ' --list-email=' + list_email + - ' --list-page=' + list_page + - ' --style-uri=' + style_uri + - ' --docdir=' + os.path.relpath('doc/html', out_dir) + - ' --tags=%s' % bld.path.get_bld().make_node('doc/tags') + - ' --index=' + str(index_file) + - (' --online' if bld.env.ONLINE_DOCS else '') + - ' ${SRC} ${TGT}') - - bld(rule = cmd, - source = os.path.join(srcpath, ttl_name), - target = [html_path, index_file], - shell = False) - - # Install documentation - bld.install_files( - os.path.join('${DOCDIR}', 'lv2', os.path.dirname(html_path)), - html_path) - - index_files.sort(key=lambda x: x.path_from(bld.path)) - bld.add_group() - - # Build extension index - bld(rule = build_index, - name = 'index', - source = ['doc/index.html.in'] + index_files, - target = 'ns/index.html') - - # Install main documentation files - bld.install_files('${DOCDIR}/lv2/aux/', 'aux/style.css') - bld.install_files('${DOCDIR}/lv2/ns/', 'ns/index.html') - - def check_links(ctx): - import subprocess - if ctx.env.LINKCHECKER: - if subprocess.call([ctx.env.LINKCHECKER[0], - '--no-status', out]): - ctx.fatal('Documentation contains broken links') - - if bld.cmd == 'build': - bld.add_post_fun(check_links) - - if bld.env.BUILD_TESTS: - # Generate a compile test file that includes all headers - def gen_build_test(task): - with open(task.outputs[0].abspath(), 'w') as out: - for i in task.inputs: - out.write('#include "%s"\n' % i.bldpath()) - out.write('int main(void) { return 0; }\n') - - bld(rule = gen_build_test, - source = bld.path.ant_glob('lv2/**/*.h'), - target = 'build-test.c', - install_path = None) - - bld(features = 'c cprogram', - source = bld.path.get_bld().make_node('build-test.c'), - target = 'build-test', - includes = '.', - uselib = 'LV2', - install_path = None) - - if 'COMPILER_CXX' in bld.env: - bld(rule = gen_build_test, - source = bld.path.ant_glob('lv2/**/*.h'), - target = 'build-test.cpp', - install_path = None) - - bld(features = 'cxx cxxprogram', - source = bld.path.get_bld().make_node('build-test.cpp'), - target = 'build-test-cpp', - includes = '.', - uselib = 'LV2', - install_path = None) - - if bld.env.BUILD_BOOK: - # Build "Programming LV2 Plugins" book from plugin examples - bld.recurse('plugins') - - -class LintContext(Build.BuildContext): - fun = cmd = 'lint' - - -def lint(ctx): - "checks code for style issues" - import subprocess - import glob - - st = 0 - - if "FLAKE8" in ctx.env: - Logs.info("Running flake8") - st = subprocess.call([ctx.env.FLAKE8[0], - "wscript", - "--ignore", - "E101,E129,W191,E221,W504,E251,E241,E741"]) - else: - Logs.warn("Not running flake8") - - if "IWYU_TOOL" in ctx.env: - Logs.info("Running include-what-you-use") - cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build"] - output = subprocess.check_output(cmd).decode('utf-8') - if 'error: ' in output: - sys.stdout.write(output) - st += 1 - else: - Logs.warn("Not running include-what-you-use") - - if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]: - Logs.info("Running clang-tidy") - sources = glob.glob('**/*.h', recursive=True) - sources = list(map(os.path.abspath, sources)) - procs = [] - for source in sources: - cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", source] - procs += [subprocess.Popen(cmd, cwd="build")] - - for proc in procs: - stdout, stderr = proc.communicate() - st += proc.returncode - else: - Logs.warn("Not running clang-tidy") - - if st != 0: - sys.exit(st) - - -def test_vocabularies(check, specs, files): - import rdflib - - foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/') - lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#') - owl = rdflib.Namespace('http://www.w3.org/2002/07/owl#') - rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') - rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#') - - # Check if this is a stable LV2 release to enable additional tests - version_tuple = tuple(map(int, VERSION.split("."))) - is_stable = version_tuple[1] % 2 == 0 and version_tuple[2] % 2 == 0 - - # Check that extended documentation is not in main specification file - for spec in specs: - path = str(spec.abspath()) - name = os.path.basename(path) - name = 'lv2core' if name == 'core' else name - vocab = os.path.join(path, name + '.ttl') - - spec_model = rdflib.ConjunctiveGraph() - spec_model.parse(vocab, format='n3') - - def has_statement(s, p, o): - for t in spec_model.triples([s, p, o]): - return True - - return False - - check(lambda: not has_statement(None, lv2.documentation, None), - name = name + ".ttl does not contain lv2:documentation") - - # Check specification manifests - for spec in specs: - path = str(spec.abspath()) - manifest_path = os.path.join(path, 'manifest.ttl') - manifest_model = rdflib.ConjunctiveGraph() - manifest_model.parse(manifest_path, format='n3') - - uri = manifest_model.value(None, rdf.type, lv2.Specification) - minor = manifest_model.value(uri, lv2.minorVersion, None) - micro = manifest_model.value(uri, lv2.microVersion, None) - check(lambda: uri is not None, - name = manifest_path + " has a lv2:Specification") - check(lambda: minor is not None, - name = manifest_path + " has a lv2:minorVersion") - check(lambda: micro is not None, - name = manifest_path + " has a lv2:microVersion") - - if is_stable: - check(lambda: int(minor) > 0, - name = manifest_path + " has even non-zero minor version") - check(lambda: int(micro) % 2 == 0, - name = manifest_path + " has even micro version") - - # Load everything into one big model - model = rdflib.ConjunctiveGraph() - for f in files: - model.parse(f, format='n3') - - # Check that all named and typed resources have labels and comments - for r in sorted(model.triples([None, rdf.type, None])): - subject = r[0] - if (type(subject) == rdflib.term.BNode or - foaf.Person in model.objects(subject, rdf.type)): - continue - - def has_property(subject, prop): - return model.value(subject, prop, None) is not None - - check(lambda: has_property(subject, rdfs.label), - name = '%s has rdfs:label' % subject) - - if check(lambda: has_property(subject, rdfs.comment), - name = '%s has rdfs:comment' % subject): - comment = str(model.value(subject, rdfs.comment, None)) - - check(lambda: comment.endswith('.'), - name = "%s comment ends in '.'" % subject) - check(lambda: comment.find('\n') == -1, - name = "%s comment contains no newlines" % subject) - check(lambda: comment == comment.strip(), - name = "%s comment has stripped whitespace" % subject) - - # Check that lv2:documentation, if present, is proper Markdown - documentation = model.value(subject, lv2.documentation, None) - if documentation is not None: - check(lambda: documentation.datatype == lv2.Markdown, - name = "%s documentation is explicitly Markdown" % subject) - check(lambda: str(documentation).startswith('\n\n'), - name = "%s documentation starts with blank line" % subject) - check(lambda: str(documentation).endswith('\n\n'), - name = "%s documentation ends with blank line" % subject) - - # Check that all properties are either datatype or object properties - for r in sorted(model.triples([None, rdf.type, rdf.Property])): - subject = r[0] - if str(subject) == 'http://lv2plug.in/ns/ext/patch#value': - continue # patch:value is just a "promiscuous" rdf:Property - - types = list(model.objects(subject, rdf.type)) - - check(lambda: ((owl.DatatypeProperty in types) or - (owl.ObjectProperty in types) or - (owl.AnnotationProperty in types)), - name = "%s is a Datatype/Object/Annotation property" % subject) - - -def test(tst): - import tempfile - - with tst.group("Data") as check: - specs = (tst.path.ant_glob('lv2/*', dir=True)) - schemas = list(map(str, tst.path.ant_glob("schemas.lv2/*.ttl"))) - spec_files = list(map(str, tst.path.ant_glob("lv2/**/*.ttl"))) - plugin_files = list(map(str, tst.path.ant_glob("plugins/**/*.ttl"))) - bld_files = list(map(str, tst.path.get_bld().ant_glob("**/*.ttl"))) - - if "SERDI" in tst.env and sys.platform != 'win32': - for f in spec_files: - with tempfile.NamedTemporaryFile(mode="w") as tmp: - base_dir = os.path.dirname(f) - cmd = tst.env.SERDI + ["-o", "turtle", f, base_dir] - check(cmd, stdout=tmp.name) - check.file_equals(f, tmp.name) - - if "SORD_VALIDATE" in tst.env: - all_files = schemas + spec_files + plugin_files + bld_files - check(tst.env.SORD_VALIDATE + all_files) - - if "CODESPELL" in tst.env: - spell_ignore_paths = [ - "doc/pygments.css", - "schemas.lv2/doap.ttl", - ] - - check(tst.env.CODESPELL + [ - "-d", - "-q", "3", - "-S", ','.join(tst.src_path(p) for p in spell_ignore_paths), - tst.src_path("lv2specgen/*.*"), - tst.src_path("doc"), - tst.src_path("lv2"), - tst.src_path("plugins"), - tst.src_path("schemas.lv2"), - tst.src_path("scripts"), - ]) - - try: - test_vocabularies(check, specs, spec_files) - except ImportError as e: - Logs.warn('Not running vocabulary tests (%s)' % e) - - with tst.group('Unit') as check: - pattern = tst.env.cprogram_PATTERN % '**/*-test' - for test in tst.path.get_bld().ant_glob(pattern): - check([str(test)]) - - -class Dist(Scripting.Dist): - def execute(self): - 'Execute but do not call archive() since dist() has already done so.' - self.recurse([os.path.dirname(Context.g_module.root_path)]) - - def get_tar_path(self, node): - 'Resolve symbolic links to avoid broken links in tarball.' - return os.path.realpath(node.abspath()) - - -class DistCheck(Dist, Scripting.DistCheck): - def execute(self): - Dist.execute(self) - self.check() - - def archive(self): - Dist.archive(self) - - -def _get_news_entries(ctx): - from waflib.extras import autoship - - # Get project-level news entries - lv2_entries = autoship.read_ttl_news('lv2', - ['lv2/core/meta.ttl', - 'lv2/core/people.ttl'], - dist_pattern = dist_pattern) - - release_pattern = r'http://lv2plug.in/spec/lv2-([0-9\.]*).tar.bz2' - current_version = sorted(lv2_entries.keys(), reverse=True)[0] - - # Add items from every specification - for specdir in specdirs(ctx.path): - name = os.path.basename(specdir.abspath()) - files = list(ttl_files(ctx.path, specdir)) - if name == "core": - files = [f for f in files if (not f.endswith('/meta.ttl') and - not f.endswith('/people.ttl') and - not f.endswith('/manifest.ttl'))] - - entries = autoship.read_ttl_news(name, files) - - def add_items(lv2_version, name, items): - for item in items: - lv2_entries[lv2_version]["items"] += ["%s: %s" % (name, item)] - - if entries: - latest_revision = sorted(entries.keys(), reverse=True)[0] - for revision, entry in entries.items(): - if "dist" in entry: - match = re.match(release_pattern, entry["dist"]) - if match: - # Append news items to corresponding LV2 version - version = tuple(map(int, match.group(1).split('.'))) - add_items(version, name, entry["items"]) - - elif revision == latest_revision: - # Not-yet-released development version, append to current - add_items(current_version, name, entry["items"]) - - # Sort news items in each versions - for revision, entry in lv2_entries.items(): - entry["items"].sort() - - return lv2_entries - - -def posts(ctx): - "generates news posts in Pelican Markdown format" - - from waflib.extras import autoship - - try: - os.mkdir(os.path.join(out, 'posts')) - except Exception: - pass - - autoship.write_posts(_get_news_entries(ctx), - os.path.join(out, 'posts'), - {'Author': 'drobilla'}) - - -def news(ctx): - """write an amalgamated NEWS file to the source directory""" - - from waflib.extras import autoship - - autoship.write_news(_get_news_entries(ctx), 'NEWS') - - -def dist(ctx): - news(ctx) - ctx.archive() - - -def distcheck(ctx): - news(ctx) - ctx.archive() |