aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitlab-ci.yml181
-rw-r--r--.gitmodules3
-rw-r--r--INSTALL.md72
-rw-r--r--README.md93
-rw-r--r--doc/c/doxy-style.css (renamed from doc/doxy-style.css)0
-rw-r--r--doc/c/footer.html (renamed from doc/footer.html)0
-rw-r--r--doc/c/header.html (renamed from doc/header.html)0
-rw-r--r--doc/c/layout.xml (renamed from doc/layout.xml)0
-rw-r--r--doc/c/mainpage.md (renamed from doc/mainpage.md)0
-rw-r--r--doc/c/meson.build41
-rw-r--r--doc/c/reference.doxygen.in (renamed from doc/reference.doxygen.in)18
-rw-r--r--doc/meson.build23
-rw-r--r--doc/ns/ext/meson.build13
-rw-r--r--doc/ns/extensions/meson.build13
-rw-r--r--doc/ns/meson.build33
-rw-r--r--lv2.pc.in7
-rw-r--r--lv2/atom/meson.build61
-rw-r--r--lv2/buf-size/meson.build40
-rw-r--r--lv2/core/meson.build44
-rw-r--r--lv2/core/meta.ttl2
-rw-r--r--lv2/data-access/meson.build40
-rw-r--r--lv2/dynmanifest/meson.build40
-rw-r--r--lv2/event/meson.build41
-rw-r--r--lv2/instance-access/meson.build40
-rw-r--r--lv2/log/meson.build41
-rw-r--r--lv2/meson.build52
-rw-r--r--lv2/midi/meson.build40
-rw-r--r--lv2/morph/meson.build40
-rw-r--r--lv2/options/meson.build40
-rw-r--r--lv2/parameters/meson.build40
-rw-r--r--lv2/patch/meson.build40
-rw-r--r--lv2/port-groups/meson.build40
-rw-r--r--lv2/port-props/meson.build40
-rw-r--r--lv2/presets/meson.build40
-rw-r--r--lv2/resize-port/meson.build40
-rw-r--r--lv2/state/meson.build40
-rw-r--r--lv2/time/meson.build40
-rw-r--r--lv2/ui/meson.build40
-rw-r--r--lv2/units/meson.build40
-rw-r--r--lv2/uri-map/meson.build40
-rw-r--r--lv2/urid/meson.build40
-rw-r--r--lv2/worker/meson.build40
-rwxr-xr-xlv2specgen/lv2specgen.py160
-rw-r--r--lv2specgen/meson.build27
-rw-r--r--meson.build194
-rw-r--r--meson/library/meson.build30
-rw-r--r--meson/suppressions/meson.build129
-rw-r--r--meson/warnings/meson.build256
-rw-r--r--meson_options.txt23
-rw-r--r--plugins/eg-amp.lv2/meson.build41
l---------plugins/eg-amp.lv2/waf1
-rw-r--r--plugins/eg-amp.lv2/wscript51
-rw-r--r--plugins/eg-fifths.lv2/meson.build41
l---------plugins/eg-fifths.lv2/waf1
-rw-r--r--plugins/eg-fifths.lv2/wscript49
-rw-r--r--plugins/eg-metro.lv2/meson.build41
l---------plugins/eg-metro.lv2/waf1
-rw-r--r--plugins/eg-metro.lv2/wscript50
-rw-r--r--plugins/eg-midigate.lv2/meson.build41
l---------plugins/eg-midigate.lv2/waf1
-rw-r--r--plugins/eg-midigate.lv2/wscript49
-rw-r--r--plugins/eg-params.lv2/meson.build41
-rw-r--r--plugins/eg-params.lv2/wscript49
-rw-r--r--plugins/eg-sampler.lv2/meson.build44
l---------plugins/eg-sampler.lv2/waf1
-rw-r--r--plugins/eg-sampler.lv2/wscript64
-rw-r--r--plugins/eg-scope.lv2/meson.build41
-rw-r--r--plugins/eg-scope.lv2/wscript56
-rw-r--r--plugins/meson.build82
-rw-r--r--plugins/wscript45
-rw-r--r--schemas.lv2/meson.build16
-rwxr-xr-xscripts/lv2_build_index.py252
-rwxr-xr-xscripts/lv2_check_specification.py248
-rwxr-xr-xscripts/lv2_check_syntax.py82
-rwxr-xr-xscripts/lv2_write_news.py258
-rw-r--r--scripts/meson.build9
-rw-r--r--test/.clang-tidy15
-rw-r--r--test/meson.build110
-rw-r--r--test/test_build.c52
-rw-r--r--test/test_build.cpp67
-rw-r--r--util/meson.build12
-rwxr-xr-xwaf27
m---------waflib0
-rw-r--r--wscript850
85 files changed, 3554 insertions, 1593 deletions
diff --git a/.gitignore b/.gitignore
index 9a895df..b532cce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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/
diff --git a/README.md b/README.md
index 0a4b4ea..5f4c438 100644
--- a/README.md
+++ b/README.md
@@ -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',
+)
diff --git a/waf b/waf
deleted file mode 100755
index 58d14c3..0000000
--- a/waf
+++ /dev/null
@@ -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()