pax_global_header00006660000000000000000000000064144140216230014507gustar00rootroot0000000000000052 comment=1aeda599b10ac911f66f41e15076e90465767f60 muon-0.2.0/000077500000000000000000000000001441402162300124645ustar00rootroot00000000000000muon-0.2.0/.builds/000077500000000000000000000000001441402162300140245ustar00rootroot00000000000000muon-0.2.0/.builds/alpine.yml000066400000000000000000000056111441402162300160220ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: alpine/latest secrets: - 2fe5fef6-db89-4410-8147-07e314589d18 - 18083346-dfba-4050-bc05-413561f99228 repositories: edge_testing: > http://dl-cdn.alpinelinux.org/alpine/edge/testing https://muon.build/nothing ../../../dev/null community: > http://dl-cdn.alpinelinux.org/alpine/latest-stable/community https://muon.build/nothing ../../../dev/null packages: - curl-dev - libarchive-dev - pkgconf-dev - samurai - python3 # for project tests, meson-reference.3, and parts of the website - py3-yaml # for meson-reference.3 - scdoc # for meson.build.5 and muon.1 - mandoc # for html man pages - reuse # for licensing compliance # alternative c compilers - clang - tcc # for static builds - acl-static - brotli-static - bzip2-static - curl-static - expat-static - libarchive-static - lz4-static - nghttp2-static - openssl-libs-static - xz-static - zlib-static - zstd-dev # for releases - rsync sources: - https://git.sr.ht/~lattis/muon environment: PKG_CONFIG_PATH: /usr/lib/pkgconfig tasks: - reuse: | cd muon reuse lint - push_to_gh_mirror: | cd muon .builds/push_to_gh_mirror.sh - kickoff_custom_ci: | cd muon .builds/solaris11.sh submit - build_gcc: | cd muon CC=gcc .builds/fullbootstrap.sh -Dbuildtype=release -Dstatic=true -Dwebsite=true - build_tcc: | cd muon CC=tcc .builds/bootstrap.sh build-tcc - test_gcc: | cd muon/build CC=gcc ./muon test -j$(nproc) -d dots - test_clang: | cd muon/build CC=clang ./muon test -j$(nproc) -d dots - build_small: | cd muon CC=gcc build/muon setup \ -Dbuildtype=minsize \ -Dstatic=true \ -Dsamurai=enabled \ -Dlibcurl=disabled \ -Dlibarchive=disabled \ build-small samu -C build-small - release: | cd muon .builds/git_archive_with_samurai.sh muon-edge+samurai .builds/prepare_release_docs.sh build .builds/prepare_binary.sh build edge-amd64-linux-static .builds/prepare_binary.sh build-small edge-amd64-linux-static-small .builds/deploy.sh / -r --delete build/doc/website .builds/deploy.sh /releases/edge -r --delete build/doc/docs .builds/deploy.sh /releases/edge \ build/muon-edge-amd64-linux-static \ build/muon-edge-amd64-linux-static.md5 \ build-small/muon-edge-amd64-linux-static-small \ build-small/muon-edge-amd64-linux-static-small.md5 \ muon-edge+samurai.tar.gz artifacts: - muon/build/muon-edge-amd64-linux-static - muon/build/muon-edge-amd64-linux-static.md5 - muon/build-small/muon-edge-amd64-linux-static-small - muon/build-small/muon-edge-amd64-linux-static-small.md5 - muon/build/doc/docs/man.tar.gz - muon/muon-edge+samurai.tar.gz muon-0.2.0/.builds/bootstrap.sh000077500000000000000000000003251441402162300164000ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux # initial build ./bootstrap.sh "$1" # get curl and zlib build/muon setup "$1" samu -C "$1" muon-0.2.0/.builds/debian.yml000066400000000000000000000012361441402162300157730ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: debian/stable packages: - ninja-build - clang - llvm sources: - https://git.sr.ht/~lattis/muon tasks: - build: | cd muon ./bootstrap.sh build build/muon setup build ninja -C build build/muon setup -Db_sanitize=address,undefined build_asan_ubsan ninja -C build_asan_ubsan CC=clang build/muon setup -Db_sanitize=memory build_memsan ninja -C build_memsan - test: | cd ~/muon/build_asan_ubsan ./muon test -j$(nproc) -d dots cd ~/muon/build_memsan ./muon test -j$(nproc) -d dots muon-0.2.0/.builds/deploy.sh000077500000000000000000000005041441402162300156560ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux if [ "$(git rev-parse master)" != "$(git rev-parse HEAD)" ]; then exit 0 fi sshopts="-o StrictHostKeyChecking=no -p 2975" dest=$1 shift rsync --rsh="ssh $sshopts" "$@" deploy@mochiro.moe:muon"$dest" muon-0.2.0/.builds/fullbootstrap.sh000077500000000000000000000003471441402162300172670ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux .builds/bootstrap.sh build # enable samurai wrap build/muon setup -Dsamurai=enabled "$@" build samu -C build muon-0.2.0/.builds/git_archive_with_samurai.sh000077500000000000000000000005421441402162300214240ustar00rootroot00000000000000#!/bin/sh -eu # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only archive="$1" git archive --format=tar --prefix="$archive/" HEAD > "${archive}.tar" find subprojects/samurai -type f | tar -u -T - \ --transform="s|^|$archive/|g" \ -f "${archive}.tar" \ --owner=0 \ --group=0 gzip -f "${archive}.tar" muon-0.2.0/.builds/netbsd.yml000066400000000000000000000010331441402162300160230ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only image: netbsd/latest packages: - curl - libarchive - pkgconf - samurai sources: - https://git.sr.ht/~lattis/muon tasks: - build: | cd muon # TODO remove this when we implement rpaths export LD_LIBRARY_PATH=/usr/pkg/lib .builds/fullbootstrap.sh - test: | cd muon/build # TODO remove this when we implement rpaths export LD_LIBRARY_PATH=/usr/pkg/lib ./muon test -d dots -s lang muon-0.2.0/.builds/prepare_binary.sh000077500000000000000000000003471441402162300173710ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux build=$1 suffix=$2 cd "$build" strip muon mv muon "muon-$suffix" md5sum "muon-$suffix" > "muon-$suffix.md5" muon-0.2.0/.builds/prepare_release_docs.sh000077500000000000000000000005351441402162300205340ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux build=$1 cd "$build/doc" mkdir man cp meson.build.5 muon.1 man tar cvf man.tar man/* gzip man.tar mkdir docs cp website/status.css docs mv website/status.html man.tar.gz docs rm -r website/version_info.py website/__pycache__ muon-0.2.0/.builds/push_to_gh_mirror.sh000077500000000000000000000006161441402162300201170ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only set -eux cat >> ~/.ssh/config < # SPDX-License-Identifier: GPL-3.0-only set -eu build_log="$HOME/build_log.txt" send_status() { echo "$1" > status rsync "$build_log" status deploy@mochiro.moe:muon/ci/solaris11/ } build() { date uname -a set -x git clone https://git.sr.ht/~lattis/muon "$tmp" cd "$tmp" git checkout $1 export CC=gcc export CFLAGS="-D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__" CURLOPTS=-k tools/bootstrap_ninja.sh build ./bootstrap.sh build build/muon setup -Dbestline=disabled -Dsamurai=enabled build build/samu -C build build/muon -C build test -d dots -s lang -j$(nproc) } submit() { cat .builds/solaris11.sh | ssh \ -oPubkeyAcceptedKeyTypes=+ssh-rsa \ -oStrictHostKeyChecking=no \ -oHostKeyAlgorithms=ssh-rsa \ lattis@gcc211.fsffrance.org \ nohup /bin/sh -s receive "$(git rev-parse @)" } _receive() { echo "build $1 received, logging to $build_log" exec >"$build_log" exec 2>&1 send_status pending dir=ci/muon tmp="ci/muon/$(date +%s)" mkdir -p "$tmp" if build "$1"; then send_status ok else send_status failed fi cd rm -rf "$tmp" } receive() { _receive "$@"& } command="$1" shift $command "$@" muon-0.2.0/.editorconfig000066400000000000000000000004711441402162300151430ustar00rootroot00000000000000# SPDX-FileCopyrightText: Eli Schwartz # SPDX-FileCopyrightText: dffdff2423 # SPDX-License-Identifier: GPL-3.0-only root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [{*.c,*.h,*.sh}] indent_style = tab muon-0.2.0/.github/000077500000000000000000000000001441402162300140245ustar00rootroot00000000000000muon-0.2.0/.github/workflows/000077500000000000000000000000001441402162300160615ustar00rootroot00000000000000muon-0.2.0/.github/workflows/macos.yml000066400000000000000000000016521441402162300177120ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only name: macos on: push: branches: [ master ] pull_request: branches: [ master ] types: [opened, synchronize, reopened] permissions: contents: read jobs: macos: runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: bootstrap_deps run: | set -x CFLAGS=-DNO_GETLOADAVG tools/bootstrap_ninja.sh build curl -L -o build/pkgconf-1.9.3.tar.gz https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-1.9.3.tar.gz tar xvf build/pkgconf-1.9.3.tar.gz mv pkgconf-pkgconf-1.9.3 subprojects/pkgconf - name: bootstrap run: | ./bootstrap.sh build PATH="build:$PATH" CFLAGS=-DNO_GETLOADAVG build/muon setup -Dsamurai=enabled build build/samu -C build - name: test run: | build/muon -C build test || true muon-0.2.0/.github/workflows/msys2.yml000066400000000000000000000035511441402162300176650ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-FileCopyrightText: Vincent Torri # SPDX-License-Identifier: GPL-3.0-only name: msys2 on: push: branches: [ master ] pull_request: branches: [ master ] types: [opened, synchronize, reopened] jobs: mingw32: runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@master - uses: msys2/setup-msys2@v2 with: msystem: mingw32 update: true install: base-devel git mingw-w64-i686-ninja mingw-w64-i686-toolchain mingw-w64-i686-pkgconf mingw-w64-i686-curl mingw-w64-i686-libarchive - name: bootstrap run: | CC=cc CFLAGS=-std=c99 ./bootstrap.sh build # - name: muon # run: | # ./build/muon.exe setup -Dbestline=disabled build # ninja -C build mingw64: runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@master - uses: msys2/setup-msys2@v2 with: msystem: mingw64 update: true install: base-devel git mingw-w64-x86_64-ninja mingw-w64-x86_64-toolchain mingw-w64-x86_64-pkgconf mingw-w64-x86_64-curl mingw-w64-x86_64-libarchive - name: bootstrap run: | CC=cc CFLAGS=-std=c99 ./bootstrap.sh build # - name: muon # run: | # ./build/muon.exe setup -Dbestline=disabled build # ninja -C build msys: runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@master - uses: msys2/setup-msys2@v2 with: msystem: msys update: true install: base-devel git ninja gcc pkgconf libcurl-devel libarchive-devel - name: Bootstrap for msys run: | ./bootstrap.sh build ./build/muon.exe setup build ninja -C build muon-0.2.0/.reuse/000077500000000000000000000000001441402162300136655ustar00rootroot00000000000000muon-0.2.0/.reuse/dep5000066400000000000000000000006501441402162300144460ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: muon Upstream-Contact: Stone Tickle Source: https://muon.build Files: tests/project/common/* tests/project/keyval/* tests/project/nasm/* tests/project/native/* tests/project/unit/* Copyright: The Meson development team Stone Tickle License: Apache-2.0 muon-0.2.0/CONTRIBUTING.md000066400000000000000000000075451441402162300147300ustar00rootroot00000000000000 # Contributions Hello, thanks for considering contributing to muon. Please send patches and questions to <~lattis/muon@lists.sr.ht>. Before making any big changes, please send a proposal to the mailing list so I can give you some pointers, and make sure you don't waste your time. # Style Muon uses a style similar to the linux kernel. A few differences are: - the return type of a function goes on the line above its declaration. e.g. ```c int main(void) { return 0; } ``` - it still goes on the same line in a function prototype e.g. ```c int main(void); ``` - never omit braces for single statement if, else, for, while, etc. - avoid function-like macros except in exceptional cases - it is OK (but not great) if your line is more than 80 characters - please use fixed-width integer types (e.g. `uint32_t`, `uint64_t`, `uint8_t`) whenever possible In general, just try to follow the style of surrounding code. # Internals ## Error handling All errors that can be checked, should be checked. If an error is detected, an error message should be printed using `interp_error`, or if there is no source code associated with the error, `LOG_E`. The error should be immediately returned. Most functions returning a `bool` return `false` on error. The most common other type of error returning function has the return type `enum iteration_result`. These functions should return `ir_err` on error. ## Meson functions All meson functions are defined in `functions` with a separate file for each object type on which the function is defined. Functions not defined on an object are in `kernel.c`. If the function implementation is sufficiently large, it may be broken up into a separate file under `functions//.c`. When declaring a new function, you need to add it to the "impl\_tbl" at the bottom of the file. All functions should call `interp_args()` before they do anything, even if they don't take any arguments. `interp_args` takes 3 arrays, conventionally named `an` (args, normal), `ao` (args, optional), and `akw` (args, keyword). Any of these may be NULL. In particular, if they are all NULL then the function takes no arguments, and `interp_args` will ensure this is the case. Arguments should specify what types they accept by bitwise or-ing `tc_`-prefixed types together. `an` and `ao` argument arrays *must* be terminated by an argument of type `ARG_TYPE_NULL`. `ARG_TYPE_GLOB` can be or-d with the type of the last element of `an`, and will store the remaining arguments in an `obj_array`. This is similar to e.g. `def func(a, b, *c):` in python, where `c` is the "glob" argument. You may also bitwise or any type with `ARG_TYPE_ARRAY_OF`. This "type" will cause `interp_args` to do the following things: 1. coerce single elements to arrays - `'hello' #=> ['hello']` 2. flatten arrays - `['hello', [], [['world']]] #=> ['hello', 'world']` 3. typecheck all elements of the array - given the "type" `ARG_TYPE_ARRAY_OF | obj_string`, the above examples would pass, but `['a', false]` would not. ## Workspace The workspace is a structure that contains all the data for an entire build setup, including all subprojects, AST, options, objects, strings. Most interpreter related functions take a workspace as one of their arguments. ## Objects Meson objects are created with the `make_obj` function. See `object.h` for more information. Conventionally, all objects are referred to by id of type obj rather than a pointer. ## Memory You may be wondering, what about Garbage collection? Muon's current approach is to cleanup only once at the very end. Meson is not a Turing-complete language, so we don't need to worry about long running programs. muon-0.2.0/LICENSES/000077500000000000000000000000001441402162300136715ustar00rootroot00000000000000muon-0.2.0/LICENSES/Apache-2.0.txt000066400000000000000000000240501441402162300161110ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. muon-0.2.0/LICENSES/GPL-3.0-only.txt000066400000000000000000001035561441402162300163430ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. “This License” refers to version 3 of the GNU General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . muon-0.2.0/LICENSES/MIT.txt000066400000000000000000000020661441402162300150670ustar00rootroot00000000000000MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. muon-0.2.0/LICENSES/Unlicense.txt000066400000000000000000000022731441402162300163630ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to muon-0.2.0/README.md000066400000000000000000000070221441402162300137440ustar00rootroot00000000000000 muon logo # [muon] muon is an implementation of the meson build system in c99 with minimal dependencies. ## Features - `muon analyze` - a static analyzer for meson.build files. [demo] - `muon fmt` - a meson.build code formatter - An interactive stepping debugger with the `dbg()` function. - [fast] ## Status `muon` is close to feature-complete with the core of meson for `c` and `c++`. See the [status page] for more detailed information. Things missing include: - cross-compilation support - build optimizations like pch and unity - some `b_` options - dependencies with a custom configuration tool - many modules Other differences from meson are described in `doc/differences.md` If you want to contribute, try using `muon` to build your favorite project. Patches and bug reports welcome! ## Dependencies Essential: - `c99` - a ninja-compatible build tool (`samu` can be optionally bootstrapped with `tools/bootstrap_ninja.sh`) For `pkgconf` support: - `libpkgconf` - `pkgconf` or `pkg-config` For `[wrap-file]` support: - `libcurl` - `libarchive` To build documentation: - `scdoc` for muon.1 and meson.build.5 - `python3` and `py3-yaml` for meson-reference.3 To run most project tests: - `python3` ## Install If you already have meson or muon and are not interested in bootstrapping, you can just do a typical meson configure, build, install: ``` $meson setup build cd build ninja build $meson test $meson install ``` Otherwise, you must bootstrap muon. The bootstrapping process has two stages. The first stage produces a `muon` binary capable of building itself (but not necessarily anything else). The second stage produces the final binary. Stage 1: ``` ./bootstrap.sh build ``` Optionally, if your system does not provide a ninja-compatible build tool, you may use the provided ninja bootstrapping script. ``` ./tools/bootstrap_ninja.sh build ninja=build/samu ``` Stage 2: ``` build/muon setup build $ninja -C build build/muon -C build test build/muon -C build install ``` ## Contribute Please refer to the [contributing guide] before sending patches. Send patches on the [mailing list], report issues on the [issue tracker], and discuss in [#muon on libera.chat]. ## License `muon` is licensed under the GPL version 3 (see LICENSE). Tests under `tests/project` were copied from the [meson project tests] and are licensed under [Apache 2.0]. ## Credits Although I had already had the idea to re-implement meson in C, I was initially inspired to actually go out and do it when I saw [boson]. `muon`'s code was originally based on `boson`, though has since been almost completely rewritten. [muon]: https://muon.build [samurai]: https://github.com/michaelforney/samurai [contributing guide]: https://git.sr.ht/~lattis/muon/tree/master/item/CONTRIBUTING.md [mailing list]: https://lists.sr.ht/~lattis/muon/ [issue tracker]: https://todo.sr.ht/~lattis/muon/ [#muon on libera.chat]: ircs://irc.libera.chat/#muon [meson project tests]: https://github.com/mesonbuild/meson/tree/master/test%20cases [Apache 2.0]: https://www.apache.org/licenses/LICENSE-2.0.txt [boson]: https://sr.ht/~bl4ckb0ne/boson/ [status page]: https://muon.build/releases/edge/docs/status.html [Fast]: https://github.com/annacrombie/meson-raytracer#performance [demo]: https://play.muon.build muon-0.2.0/bootstrap.sh000077500000000000000000000014331441402162300150410ustar00rootroot00000000000000#!/bin/sh # SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only # Requirements: # - c99 # - sh # Optional requirements: # - pkgconf or pkg-config # - libpkgconf set -eux dir="$1" mkdir -p "$dir" pkgconf_cmd="" if command -v pkgconf >/dev/null; then pkgconf_cmd=pkgconf elif command -v pkg-config >/dev/null; then pkgconf_cmd=pkg-config fi if [ -n "$pkgconf_cmd" ] && $pkgconf_cmd libpkgconf; then pkgconf_src="libpkgconf.c" pkgconf_cflags="$($pkgconf_cmd --cflags libpkgconf) -DBOOTSTRAP_HAVE_LIBPKGCONF" pkgconf_libs="$($pkgconf_cmd --libs libpkgconf)" else pkgconf_src="libpkgconf_null.c" pkgconf_cflags="" pkgconf_libs="" fi ${CC:-c99} ${CFLAGS:-} ${LDFLAGS:-} -Iinclude $pkgconf_cflags "src/amalgam.c" $pkgconf_libs -o "$dir/muon" muon-0.2.0/contrib/000077500000000000000000000000001441402162300141245ustar00rootroot00000000000000muon-0.2.0/contrib/muon.vim000066400000000000000000000031311441402162300156150ustar00rootroot00000000000000" SPDX-FileCopyrightText: Stone Tickle " SPDX-License-Identifier: GPL-3.0-only " Author: lattis " Description: Ale linter muon for meson files " function! ale_linters#meson#muon#GetExecutable(buffer) abort return 'muon' endfunction function! ale_linters#meson#muon#GetCommand(buffer) abort let l:executable = ale_linters#meson#muon#GetExecutable(a:buffer) let l:file = resolve(expand('%:p')) let l:cmd = ale#Escape(l:executable) let l:args = 'analyze -l' if match(l:file, '\.meson$') != -1 let l:args = l:args . 'i-' else let l:args = l:args . 'O' . ale#Escape(l:file) endif return l:cmd . ' ' . l:args endfunction function! ale_linters#meson#muon#Handle(buffer, lines) abort let l:pattern = '\v(^.*):(\d+):(\d+): (warning|error) (.*)$' let l:output = [] let l:cur_file = resolve(expand('%:p')) for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:filename = l:match[1] if l:filename == '-' let l:filename = l:cur_file endif call add(l:output, { \ 'filename': l:filename, \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, \ 'type': l:match[4] == 'warning' ? 'W' : 'E', \ 'text': l:match[5], \}) endfor return l:output endfunction call ale#linter#Define('meson', { \ 'name': 'muon', \ 'executable': function('ale_linters#meson#muon#GetExecutable'), \ 'command': function('ale_linters#meson#muon#GetCommand'), \ 'callback': 'ale_linters#meson#muon#Handle', \ 'output_stream': 'stderr', \}) " usage: " mkdir -p /path/to/vim/config/ale_linters/meson " cp /path/to/muon/contrib/muon.vim " /path/to/vim/config/ale_linters/meson/muon.vim muon-0.2.0/contrib/muon_fmt.vim000066400000000000000000000007201441402162300164640ustar00rootroot00000000000000" SPDX-FileCopyrightText: Stone Tickle " SPDX-License-Identifier: GPL-3.0-only " Author: lattis " Description: Ale fixer muon fmt for meson files " function! FormatMeson(buffer) abort return { \ 'command': 'muon fmt -' \} endfunction execute ale#fix#registry#Add('muon-fmt', 'FormatMeson', ['meson'], 'muon fmt for meson') " usage: " source /path/to/muon/contrib/muon_fmt.vim " let g:ale_fixers = { " \ 'meson': ['muon-fmt'], " } muon-0.2.0/doc/000077500000000000000000000000001441402162300132315ustar00rootroot00000000000000muon-0.2.0/doc/differences.md000066400000000000000000000105741441402162300160370ustar00rootroot00000000000000 # Differences between muon and Meson This document describes functional differences between muon and Meson. None of these is a set-in-stone design decision, just a reflection of the current state of affairs. This document is also not exhaustive, but is a best-effort list. Some other small differences may be found by searching the tests/project for "# different than meson" comments. ## nested subproject promotion Meson performs nested subproject promotion. This means that nested subprojects become top-level subprojects, and all subprojects share the same namespace. For example, given the following project structure: ``` . ├── meson.build └── subprojects ├── a │ ├── meson.build │ └── subprojects │ └── b │ └── meson.build └── b └── meson.build ``` The order of `subproject` calls determines which subprojects will be used: ```meson project('main') # This causes all subprojects under subprojects/a/subprojects/ to be "promoted" subproject('a') # This will now use subprojects/a/subprojects/b, instead of subprojects/b subproject('b') ``` muon does not perform subproject promotion. ## malformed escape sequences Meson silently accepts malformed escape sequences and outputs them literally, removing the leading escape character. For example: ```meson '\c' # becomes 'c' '\Uabcdefghi' # becomes 'Uabcdefghi' '\xqr' # becomes 'xqr' ``` In muon, malformed escape sequences are parse errors. ## format strings Format strings in various parts of Meson use `@` as the delimiter. The behavior is inconsistent, `configure_file()` recognizes `\` as an escape character, but format strings in `string.format()` and `custom_target` command arguments do not. `configure_file()` will also warn you about invalid substitutions, and will remove them in the output, `string.format()` will error on invalid substitutions, and `custom_target` command arguments will be silently treated as literals if they are invalid substitutions (e.g. `@BAZ@`). Because some projects rely on the above custom target command argument behaviour, muon merely adds a warning for invalid substitutions. In all other cases muon will raise error. ## `custom_target` replaces backslashes with slashes in the command arguments In Meson, all backslashes in `custom_target` command line arguments are blindly replaced to forward slashes. This behavior is not present in muon. Reference: ## `build_target()` functions with empty sources Meson allows you to create build targets (`executable()`, `shared_library()`, `static_library()`, etc.) without specifying any sources. In muon this is an error. ## global compiler cache Meson maintains a global compiler cache, which means that all languages added by subprojects are available to the main project and vice-versa. This can hide bugs that will surface if the subproject is built by itself, or subproject calls are rearranged. ## run\_command() cwd Meson executes run\_command() commands in the current subdirectory, while muon executes them in the project root. Neither behaviour should be relied upon however, since the docs say that it runs commands from an unspecified directory. ## backslash escaping in compiler defines Meson replaces `\` with `\\` in compiler defines. This is legacy behavior that prevents you from using things like C escapes (e.g. `\n`) in compiler defines, at the benefit of making it easier to use windows paths. See meson commit aca93df184a32ed7faf3636c0fbe90d05cb67857 for more information: > Jon Turney: > Now that all command-line escaping for ninja is dealt with in the ninja > backend, escape_extra_args() shouldn't need to do anything. > > But tests of existing behaviour rely on all backslashes in defines being > C escaped: This means that Windows-style paths including backslashes can > be safely used, but makes it impossible to have a define containing a C > escape. ## MESONINTROSPECT Since muon does not offer an introspection subcommand, `MESONINTROSPECT` is not set in the environment of run\_command, test, custom\_target, etc. `MUON_PATH` is provided for users who are waiting for and are (ab)using `MESONINTROSPECT` for this purpose. muon-0.2.0/doc/meson.build000066400000000000000000000021151441402162300153720ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only scdoc = find_program('scdoc', required: get_option('docs')) if not scdoc.found() subdir_done() endif man_pages = { 'muon': 1, 'meson.build': 5, } man_page_targets = {} foreach page, section : man_pages name = f'@page@.@section@' input = f'@name@.scd' tgt = custom_target( input, input: files(input), output: name, capture: true, feed: true, command: scdoc, install: true, install_dir: get_option('mandir') / 'man@0@'.format(section), ) man_page_targets += {name: tgt} endforeach meson_proj = dependency('', required: false) if python3.found() meson_proj = subproject('meson-docs', required: false) endif if meson_proj.found() man_page_targets += { 'meson-reference.3': meson_proj.get_variable('meson_reference_3'), } else warning('meson-docs not found, meson-reference.3 will not be built') endif if get_option('website') subdir('website') endif summary('docs', true) muon-0.2.0/doc/meson.build.5.scd000066400000000000000000000143551441402162300163160ustar00rootroot00000000000000meson.build(5) ; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only # NAME meson.build - a build system dsl # DESCRIPTION The meson dsl is a dynamically typed language, similar to other interpreted languages like python and ruby. All objects are immutable. Functions cannot be defined, only built-in functions are available. - *STRUCTURE* - overview of source and project layout - *TYPES* - information on all primitive types - *SYNTAX* - description of syntax and keywords # STRUCTURE All meson projects must have a file called `meson.build` at the project root. This is the root build file. This file must have a call to the `project()` function as its first statement. Additional `meson.build` files may reside in any subdirectories of the project. These build files are then executed with the `subdir()` function. # TYPES The meson build system dsl contains most of the common types that would be expected: - booleans (written as `true` and `false`) - integers - strings - arrays - dictionaries ## Booleans Booleans are either `true` or `false`. *OPERATORS* - `and` - logical and - `or` - logical or - `not` - logical and ## Integers You can specify an integer literal using decimal, hexadecimal, octal, and binary. ``` int_1 = 1 int_42 = 42 int_255 = 0xFF int_493 = 0o755 int_1365 = 0b10101010101 ``` All common arithmetic operations are implemented for integers: addition, subtraction, division, multiplication, and modulo. *OPERATORS* - `\*` - multiplication - `/` - integer division - `%` - modulo (remainder) - `+` - addition - `-` - subtraction - `==` - equal to - `!=` - not equal to - `<` - less than - `>` - greater than - `<=` - less than or equal to - `>=` - greater than or equal to ## Strings Strings in Meson are declared with single quotes. `\` is the escape character. The full list of escape sequences is: - `\\` Backslash - `\'` Single quote - `\a` Bell - `\b` Backspace - `\f` Formfeed - `\n` Newline - `\r` Carriage Return - `\t` Horizontal Tab - `\v` Vertical Tab - `\ooo` Character with octal value ooo, up to 3 digits long - `\xhh` Character with hex value hh - `\uxxxx` Character with 16-bit hex value xxxx - `\Uxxxxxxxx` Character with 32-bit hex value xxxxxxxx - `\N{name}` Character named name in Unicode database Multi-line strings are surrounded by 3 consecutive quotes. These are raw strings that do not support the escape sequences listed above. Format strings are expressed by a placing leading `f` before the first opening quote. Inside of a format string, sequences of the form `@[a-z_]+@` will be substituted with the value of the matching variable. ``` name = 'Alice' # prints "Hello Alice" message(f'Hello @name@') ``` *OPERATORS* - `+` - concatenate two strings - `/` - concatenate two strings as if `join_paths()` was called - `[n]` - access the character at index `n` - `==` - equal to - `!=` - not equal to ## Arrays Arrays are delimited by brackets. An array can contain an arbitrary number of objects of any type. *OPERATORS* - `+` - If the rhs operand is an array, it will be joined to the lhs array. If it is a scalar, it will be appended to the lhs array. - `[n]` - access the object at index `n` - ` in ` - check if object `obj` is in `array` - ` in ` - check if object `obj` is not in `array` - `==` - equal to - `!=` - not equal to ## Dictionaries Dictionaries are delimited by curly braces. A dictionary can contain an arbitrary number of key: value pairs. Keys are required to be strings, but values can be objects of any type. Dictionaries are immutable and do not have a guaranteed order. *OPERATORS* - `+` - merge two dictionaries. In case of a conflicting key, the value from the rhs dictionary will be taken. - `[key]` - access the object with key `key` - ` in ` - check if key `key` is in `dict` - ` not in ` - check if key `key` is not in `dict` - `==` - equal to - `!=` - not equal to # SYNTAX A meson build file is composed of statements, which are terminated by newlines. Other than the statement-terminating newline, white space has no syntactic meaning. ## Comments A comment starts with the `#` character and extends until the end of the line. ## Variables A variable can contain a value of any type, and does not need to be predeclared. ``` var1 = 'hello' var2 = 102 ``` One important difference in how variables work in the dsl is that all objects are immutable. When you see an operation which appears like a mutation, actually a new object is created and assigned to the name. ``` var1 = [1, 2, 3] var2 = var1 var2 += [4] # var2 is now [1, 2, 3, 4] # var1 is still [1, 2, 3] ``` ## Function and method calls Builtin functions are called by their name followed by parenthesis containing optional, comma-separated arguments. Arguments are either positional or keyword. Keyword arguments are expressed using the keyword without quotes, followed by a colon. ``` foo() foo('bar') foo('bar', baz: true) ``` Method calls are expressed by a `.` followed by the same function call syntax as above. ``` foo.bar() foo.bar('baz', qux: false) ``` For a complete list of functions and methods, please see `meson-reference(3)`. ## If statements Start an if statement with the `if` keyword followed by a boolean expression. Further conditions can be expressed using the `elif` keyword followed by a boolean expression. The `else` keyword can be used to handle the case when no conditions are matched. An if statement is terminated with the `endif` keyword. ``` if conditon1 elif condition2 else condition3 endif ``` ## Foreach statements To loop over values in an iterable, use the `foreach` keyword followed by a comma separated list of names to assign the values of the iterable to, a colon, and an iterable expression. Only arrays and dictionaries are iterable. A foreach statement is terminated with the `endforeach` keyword. Arrays have one value to assign to. ``` foreach value : array foo(value) endforeach ``` Dictionaries have one two values to assign to. ``` foreach key, value : dictionary foo(key) bar(value) endforeach ``` Inside a `foreach` block you may use the `break` and `continue` keywords. \`break` exits the loop immediately. `continue` skips the rest of the current iteration. # SEE ALSO meson-reference(3) meson(1) muon(1) muon-0.2.0/doc/misc/000077500000000000000000000000001441402162300141645ustar00rootroot00000000000000muon-0.2.0/doc/misc/configuration_data_escaping_rules.md000066400000000000000000000013061441402162300234310ustar00rootroot00000000000000 Configuration data escaping rules: 1. You can escape the format character (@) by preceding it with a backslash. 2. Backslashes not directly preceding a format character are not modified. 3. The number of backslashes preceding a @ in the output is equal to the number of backslashes in the input divided by two, rounding down. For example, given the string "\\@" (one backslash), the output will contain no backslashes. Both "\\\\@" and "\\\\\\@" (two and three backslashes) will produce one backslash in the output "\\@". Examples: "\\@" -> "@" "\\\\@" -> "\\@" "\\\\ @" -> "\\\\ @" muon-0.2.0/doc/misc/dep_flowchart.md000066400000000000000000000030771441402162300173360ustar00rootroot00000000000000 ```mermaid flowchart TD start(for each dependency name) start --> loop loop(have another dependency name) loop --yes--> special loop --no--> notfound special(has special handling) special --yes--> found special --no--> cache cache(dependency in cache) cache --yes--> vercheck cache --no--> forcefallback vercheck(dependency version matches) vercheck --yes--> found vercheck --no--> loop forcefallback(fallback forced) forcefallback --yes--> fallback forcefallback --no--> lookup fallback(fallback keyword set) fallback --yes - use fallback from keyword--> handlefallback fallback --no--> fallbackimplicit fallbackimplicit(fallback provided by any wraps?) fallbackimplicit --yes - use fallback from wrap provides--> handlefallback fallbackimplicit --no--> fallbackimplicitname fallbackimplicitname(fallback is allowed) fallbackimplicitname --yes - use fallback based on dependency name--> handlefallback fallbackimplicitname --no--> loop handlefallback(check for any named dependency in subproject) handlefallback --found--> found handlefallback --not found--> loop lookup(lookup dependency with tool, e.g. pkgconf) lookup --found--> found lookup --not found--> fallbackafterlookup fallbackafterlookup(fallback is allowed) fallbackafterlookup --yes--> fallback fallbackafterlookup --no--> loop found(done) notfound(not found) ``` fallback allowed: allow\_fallback is set to true or requirement is required muon-0.2.0/doc/muon.1.scd000066400000000000000000000227511441402162300150500ustar00rootroot00000000000000muon(1) ; SPDX-FileCopyrightText: Stone Tickle ; SPDX-License-Identifier: GPL-3.0-only # NAME muon - a meson-compatible build system # SYNOPSIS *muon* [*-vh*] [*-C* ] [] *muon* *setup* [*-D*[subproject*:*]option*=*value...] build ++ *cd* build++ ++ *muon* *test* [options] ++ *muon* *install* [options] # DESCRIPTION *muon* interprets _source files_ written in the _meson dsl_ and produces _buildfiles_ for a backend. Currently the only supported backend is _ninja_. When building *meson* projects with *muon*, you typically first start by running the *setup* command in the project root. This will create _buildfiles_ for the backend in the _build dir_ you specify. You then invoke the backend, e.g. ``` ninja -C ``` If the project defines tests, you may run them with the *test* subcommand, and finally install the project with the *install* subcommand. # OPTIONS - *-v* - enable verbose output - *-C* - chdir to _path_ before executing a command - *-h* - print a help message # COMMANDS *muon* requires a command. All commands accept a *-h* option which prints a brief summary of their usage. ## analyze *muon* *analyze* Run a static analyzer on the current project. *OPTIONS*: - *-l* - optimize output for editor linter plugins. For example, a diagnostic is added for the subdir() callsite, so that the editor can highlight subdirs with errors in them. - *-q* - only report errors - *-O* - read project file with matching path from stdin. This is useful for editor linter plugins that run as you type on files that aren't saved to disk. - *-i* - analyze the single file _path_ in internal mode. This is useful for catching bugs in scripts that will be evaluated with *muon internal eval*. - *-W* [no-] - enable or disable a particular diagnostic, e.g. unused-variable. - *-W* list - list available diagnostics. - *-W* error - turn all warnings into errors. ## benchmark See documentation for the *test* subcommand. ## check *muon* *check* [*-p*|*-P*] Check if _filename_ parses. *OPTIONS*: - *-p* - print the parsed ast - *-P* - print the parsed formatting ast ## fmt *muon* *fmt* [*-i*] [*-q*] [*-c* ] [ [...]] Format a _source file_. The formatting is currently minimally configurable, and is based on the official meson style guide . *OPTIONS*: - *-q* - exit with 1 if files would be modified by muon fmt - *-i* - format files in-place - *-c* - read configuration from _muon\_fmt.ini_ *CONFIGURATION OPTIONS* [[ *key* :[ *type* :[ *default* :[ *description* | max_line_len : uint : 80 : The maximum length of lines before they are split | indent_by : str : ' ' : A string that will be used to indent with | space_array : bool : false : Whether to include spaces around array elements (ex. [1] vs [ 1 ]) | kwargs_force_multiline : bool : false : Make every dictionary literal and function call with kwargs expand into multiple lines. | wide_colon : bool : false : Whether to put a space before the colon operator (ex. `key : val`) | no_single_comma_function : bool : false : Don't add a trailing comma to multi-line function calls with only one argument. ## install *muon* *install* [*-n*] Installs the project. The _DESTDIR_ environment variable is respected and will prefix all installation directories if it is present. *OPTIONS*: - *-n* - dry run ## internal *muon* *internal* [] Internal contains several subcommands used by *muon* internally. These commands are subject to change at any time and this should not be relied upon. *SUBCOMMANDS*: - *eval* - evaluate a _source file_ - *exe* - execute a command - *repl* - start a _meson dsl_ repl - *dump_funcs* - output all supported functions and arguments ## internal eval *muon* *internal* *eval* [*-e*] [*-s*] [] Interpret a _source file_. The interpreter environment is substantially different from the typical environment during *setup*. Build related functions are unavailable, as well as many other functions including *subdir*. Additionally, the variable *argv* is defined as an array containing the commandline starting at . *OPTIONS*: - *-e* - lookup as an embedded script - *-s* - disable functions that are unsafe to be called at random, particularly `run_command()`. The motivation for this flag is so that automated fuzz testing can be used without accidentally executing something like `run_command('rm', '-rf', '/')`. ## internal exe *muon* *internal* *exe* [*-f* ] [*-c* ] [*-e* ] [*-a* ] [-- []] Execute with arguments . *OPTIONS*: - *-f* - pass _input file_ as stdin to - *-c* - capture stdout of and write it to _output file_ - *-e* - read and set environment variables from _env.dat_ - *-a* - read and set command from _args.dat_ ## internal repl *muon* *internal* *repl* Start a _meson dsl_ repl. The functions available are limited as with *internal eval*. ## internal dump_funcs *muon* *internal* *dump_funcs* Print all supported functions, methods, and module functions with their arguments, argument types, and return types to stdout. This subcommand is mainly useful for generating https://muon.build/status.html. ## meson *meson* ... A compatibility layer that attempts to translate all flags and operands from meson cli syntax to muon cli syntax. For example, the following two commands: ``` muon meson setup build --werror --prefix=/ muon meson test -C build --list ``` Would be translated into the following two muon versions respectively: ``` muon setup -Dwerror=true -Dprefix=/ build muon -C build test -l ``` This compatibility layer is also enabled when muon's executable is named _meson_. NOTE: This is a best-effort translation and does not guarantee or imply full cli compatibility. Many unimplemented flags are ignored and attempting to use an unsupported subcommands will result in an error. ## options *muon* *options* [*-a*] [*-m*] Lists available project options and defaults. This command may either be run from the project root or from a build directory. Running this command from a build directory highlights configured option values in the output, whereas running it from the project root causes the default value for each option to be highlighted. *OPTIONS*: - *-a* - Include builtin global and per-project options in the output. - *-m* - Only display option values that have been modified. ## samu *muon* *samu* [] Executes an embedded copy of *samu*(1). This command requires that muon was compiled with *samu* enabled. ## setup *muon* *setup* [*-D*[subproject*:*]option*=*value...] [*-c* ] [*-b*] Interpret all _source files_ and generate _buildfiles_ in _build dir_. *OPTIONS*: - *-D* [subproject*:*]option*=*value - Set build options. Options are either built in or project-defined. Subproject options can be specified by prepending the subproject's name and a colon to the *option*. This option may be specified multiple times. - *-c* - load compiler check cache dump from path. This is used internally when creating the regeneration command. - *-b* - Break on error. When this option is passed, muon will enter a debugging repl when a fatal error is encountered. From there you can inspect and modify state, and optionally continue setup. ## summary *muon* *summary* Print a previously configured project's summary. ## test *muon* *test* [*-d* ] [*-e* ] [*-f*] [*-j* ] \[*-l*] [*-R*] [*-s* ] [*-S*] [*-v [*-v*]*] Execute tests defined in _source files_. *OPTIONS*: - *-d* - Control test output. _display mode_ can be one of *auto*, *dots*, or *bar*. *dots* prints a '.' for success and 'E' for error, *bar* prints a progress bar with an error count. The default mode, *auto*, selects *bar* if the output device is a terminal or *dots* otherwise. - *-e* - Use test setup _setup_. - *-f* - Fail fast. exit after first test failure is encountered. - *-j* - Set the number of jobs used when running tests. - *-l* - List tests that would be run with the current setup, suites, etc. The format of the output is : - . - *-R* - No rebuild. Disable automatic build system invocation prior to running tests. - *-s* - Only run tests in suite _suite_. This option may be specified multiple times. - *-S* - print a summary of test results, including the duration of each test - *-v* - Increase verbosity. When passed once, print test results as they are completed. When passed twice, the stdout/stderr of tests is not captured. ## version *muon* *version* Print out version information as well as enabled features. # EXTENSIONS *muon* provides some extra functions that may be called within _source files_. They are documented below. - *dbg()* - Begin an interactive debugger. - *p(value)* - Print any value's internal representation. For example, `p('hello')` prints `'hello'`. # SEE ALSO meson.build(5) meson-reference(3) meson(1) # AUTHORS Maintained by Stone Tickle , who is assisted by other open source contributors. For more information about muon development, see . muon-0.2.0/doc/muon_fmt.ini000066400000000000000000000004261441402162300155600ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only # Example configuration for muon fmt max_line_len = 80 indent_by = ' ' space_array = false kwargs_force_multiline = false wide_colon = false no_single_comma_function = false muon-0.2.0/doc/website/000077500000000000000000000000001441402162300146735ustar00rootroot00000000000000muon-0.2.0/doc/website/index.html000066400000000000000000000065001441402162300166710ustar00rootroot00000000000000 muon

A meson-compatible build system.

muon-0.2.0/doc/website/main.css000066400000000000000000000012101441402162300163230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ body { max-width: 700px; margin: auto; padding: 10px; font-family: sans-serif; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } small { color: #e8e8e2; } } nav div { padding: 10px; padding-left: 20px; display: block; } .logo { display: flex; flex-direction: row; align-items: center; font-size: 40px; } .logo small { font-size: 10px; } .logo_text { display: flex; flex-direction: column; padding: 10px; } muon-0.2.0/doc/website/man-style.css000066400000000000000000000012651441402162300173220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ body { max-width: 100ex; font-family: monospace; font-size: 14px; } pre { color: #434241; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } pre { background-color: #191A21; color: #F8F8F2; } } .manual-text { padding: 0 9ex 1ex 4ex; } .head, .foot { width: 100%; color: #999; } .head-vol { text-align: center; } .head-rtitle { text-align: right; } h1 { font-size: 16px; } h2 { font-size: 15px; } .Bd-indent { padding-left: 4ex; } section .Ss { padding-left: 2ex; } muon-0.2.0/doc/website/meson.build000066400000000000000000000027271441402162300170450ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only mandoc = find_program('mandoc') foreach name, tgt : man_page_targets name = f'@name@.html' custom_target( name, input: tgt, output: name, capture: true, command: [mandoc, '-T', 'html', '-O', 'style=man-style.css', '@INPUT@'], build_by_default: true, ) endforeach if meson_proj.found() muon_signatures = custom_target( 'muon_signatures.txt', output: 'muon_signatures.txt', command: [muon, 'internal', 'dump_funcs'], capture: true, ) meson_signatures = meson_proj.get_variable('signatures') configure_file( configuration: version_info, input: 'version_info.py.in', output: 'version_info.py', ) env = environment() env.append('PYTHONPATH', meson.current_build_dir()) custom_target( 'status.html', output: 'status.html', command: [ python3, files('signatures_to_html.py'), muon_signatures, meson_signatures, ], capture: true, build_by_default: true, env: env, ) endif foreach f : [ 'index.html', 'muon_ci.html', 'man-style.css', 'status.css', 'main.css', 'muon_logo.svg', ] configure_file( configuration: version_info, input: f, output: f, ) endforeach summary('website', true) muon-0.2.0/doc/website/muon_ci.html000066400000000000000000000062661441402162300172240ustar00rootroot00000000000000 muon ci

muon ci

os arch status flags
Alpine x86_64
NetBSD amd64 🧪
Debian Stable amd64
Solaris 11 sun4u ... 🧪
Windows - msys2 x86_64
macOS 12 x86_64

flags

  • 🧪 - project tests are disabled
  • ⚠ - all tests are disabled
muon-0.2.0/doc/website/muon_logo.svg000066400000000000000000000155361441402162300174240ustar00rootroot00000000000000 muon-0.2.0/doc/website/signatures_to_html.py000066400000000000000000000236021441402162300211620ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only import sys import itertools from version_info import MuonVersion def parse(file): sigs = {} with open(file, "r") as f: argi = { "posargs:": 0, "varargs:": 1, "optargs:": 2, "kwargs:": 3, "returns:": 4, } fname = None args = [[], [], [], [], []] curargs = None extension = False for line in f.read().strip().split("\n"): if line[0] != " ": if fname: sigs[fname] = Sig(fname, *args, extension) fname = None args = [[], [], [], [], []] curargs = None extension = False l = line.split(":") if len(l) > 1 and l[0] == "extension": extension = True fname = l[-1] elif line[2] != " ": curargs = argi[line.strip()] else: v = line.strip() if curargs == 3: # kwargs v = v.split(": ") args[curargs].append(v) if fname: sigs[fname] = Sig(fname, *args, extension) return sigs class Sig: def empty(name): return Sig(name, [], [], [], [], [], False) def __init__(self, name, posargs, varargs, optargs, kwargs, returns, extension): self.name = name self.posargs = posargs self.varargs = varargs self.optargs = optargs self.kwargs = {self.esc(k): v for k, v in self.normalize_kw(kwargs)} if returns: self.returns = returns[0] else: self.returns = returns self.extension = extension def normalize_kw(self, kwargs): applies_to = [ "executable", "library", "static_library", "shared_library", "shared_module", "build_target", "both_libraries", ] if self.name not in applies_to: return kwargs prefixes = ["c_", "cpp_", "objc_"] delete = [] add = {} for kw, v in kwargs: for pre in prefixes: if kw.startswith(pre): delete.append(kw) add["_" + kw[len(pre) :]] = v break add = [[k, v] for k, v in add.items()] return [x for x in kwargs if x[0] not in delete] + add def esc(self, c): return str(c).replace("<", "<").replace(">", ">") def arg_count(self): return ( len(self.posargs) + len(self.varargs) + len(self.optargs) + len(self.kwargs) ) muon = parse(sys.argv[1]) meson = parse(sys.argv[2]) class Table: def __init__(self, thead): self.thead = thead self.rows = [] def __add__(self, other): self.rows.append(other) return self def html(self): return ( "" + self.thead.html() + "" + "".join([r.html() for r in self.rows]) + "
" ) class Row: def __init__(self, *cols): self.cols = cols def html(self): return ( "" + "".join(["" + str(c) + "" for c in self.cols]) + "" ) class FuncInfo: def __init__(self, name): self.name = name self.me = None self.mu = None def _parse_type(t, in_container=False): parsed = [] name = "" i = 0 while i < len(t): c = t[i] if c == "[": (n, sub) = _parse_type(t[i + 1 :], in_container=True) parsed.append( ( name, sub, ) ) name = "" i += n + 1 continue elif c == "]": if name: parsed.append(name) return (i + 1, parsed) elif c == " ": i += 1 continue elif c == "|": if name: parsed.append(name) name = "" else: name += c i += 1 if name: parsed.append(name) return (i, parsed) def parse_type(t): if t is None: return [] (_, parsed) = _parse_type(t) return parsed # Attempt to detect if a signature indicates listification and remove the # duplicate part def normalize_listify(tp): if not tp: return tp nl = [] l = set() for t in tp: if type(t) is tuple: if t[0] == "list": l = t[1] else: return tp else: nl.append(t) if set(nl) == set(l): return [("list", nl)] else: return tp def assemble_type(t): if type(t) is list: def sort_func(v): assert type(v) is not list if type(v) is tuple: return v[0] else: return v t.sort(key=sort_func) return " | ".join(assemble_type(x) for x in t) elif type(t) is tuple: return t[0] + "[" + assemble_type(t[1]) + "]" else: return t def neutral(text): return f'{text}' def positive(text): return f'{text}' def negative(text): return f'{text}' def tdiff(a, b): s = [] for t in a: if type(t) is tuple: other = None for u in b: if type(u) is tuple and t[0] == u[0]: other = u[1] break if other: s.append(neutral(f"{t[0]}[{tdiff(t[1], other)}]")) else: s.append(positive(f"{t[0]}[{assemble_type(t[1])}]")) else: if t in b: s.append(neutral(t)) else: s.append(positive(t)) return " | ".join(s) def normalize_types(tp): conts = [] ntp = set() for t in tp: if type(t) is tuple: sub = normalize_types(t[1]) if not sub: ntp |= set([t[0]]) else: conts.append((t[0], sub)) else: ntp |= set( { "custom_idx": ["file"], "extracted_obj": ["file"], "void": [], "tgt": ["build_tgt", "custom_tgt", "both_libs"], "lib": ["build_tgt", "both_libs"], "exe": ["build_tgt"], }.get(t, [t]) ) def sort_func(v): assert type(v) is not list if type(v) is tuple: return v[0] else: return v return sorted(list(ntp) + conts, key=sort_func) def typecomp(mu, me): a = normalize_listify(normalize_types(parse_type(mu))) b = normalize_listify(normalize_types(parse_type(me))) return (tdiff(a, b), tdiff(b, a)) func_tbl = Table(Row("function", "status", "muon return", "meson return")) arg_tbls = [] all_funcs = set(muon.keys()) | set(meson.keys()) methods = set([f for f in all_funcs if "." in f]) kernel = all_funcs - methods for f in sorted(kernel) + sorted(methods): if f in ["custom_idx.full_path", "custom_tgt.[index]"]: continue r = FuncInfo(f) if f in muon: flink = f'{f}' r.mu = muon[f] else: flink = f if f in meson: r.me = meson[f] support = positive("supported") if f not in muon: r.mu = Sig.empty(f) support = negative("unsupported") elif f not in meson: r.me = Sig.empty(f) if muon[f].extension: support = "muon extension" else: support = positive("supported*") func_tbl += Row(flink, support, *typecomp(r.mu.returns, r.me.returns)) t = Table(Row("kind", "keyword", "muon type", "meson type")) for k in ["posarg", "vararg", "optarg"]: for a, b in itertools.zip_longest( getattr(r.mu, k + "s"), getattr(r.me, k + "s") ): t += Row(k, "", *typecomp(a, b)) for k in sorted(set(r.me.kwargs.keys()) | set(r.mu.kwargs.keys())): t1 = None t2 = None if k in r.mu.kwargs: t1 = r.mu.kwargs[k] if k in r.me.kwargs: t2 = r.me.kwargs[k] t += Row("kwarg", k, *typecomp(t1, t2)) if f in muon: arg_tbls.append((r, t)) module_tbl = Table(Row("module", "status")) for m in ["fs", "keyval", "pkgconfig", "sourceset"]: module_tbl += Row(m, positive("supported")) for m in ["python3", "python"]: module_tbl += Row(m, "partial") for m in [ "cmake", "dlang", "gnome", "hotdoc", "i18n", "java", "modtest", "qt", "qt4", "qt5", "qt6", "unstable-cuda", "unstable-external_project", "unstable-icestorm", "unstable-rust", "unstable-simd", "unstable-wayland", "windows", ]: module_tbl += Row(m, negative("unsupported")) print( """ muon implementation status
""" ) print('
') print("

Version

") print(f"

muon version {MuonVersion.version}-{MuonVersion.vcs_tag}

") print(f"

meson compat version {MuonVersion.meson_compat}

") print("
") print('
') print(f"

Modules

") print(module_tbl.html()) print(f"

Functions and methods

") print(func_tbl.html()) print("
") for r, at in arg_tbls: print("
") print('
') print(f'

{r.name}

') if at.rows: print(at.html()) else: print("no arguments") print("
") print("
") muon-0.2.0/doc/website/status.css000066400000000000000000000013431441402162300167310ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ .positive { background-color: #50FA7B; color: #21222C; } .negative { background-color: #FF5555; color: #21222C; } tr:nth-child(odd) { background-color: #F8F8F2; } @media (prefers-color-scheme: dark) { body { background-color: #282A36; color: #F8F8F2; } a { color: #8BE9FD; } tr:nth-child(odd) { background-color: #424450; } } body { margin: 0; font-family: sans-serif; } .wrapper { width: auto; } @media screen and (min-width: 769px) { .wrapper { margin-left: 100px; margin-right: 100px; } } table { width: 100%; } .item { width: 100%; } muon-0.2.0/doc/website/version_info.py.in000066400000000000000000000003071441402162300203520ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only class MuonVersion: version = "@version@" vcs_tag = "@vcs_tag@" meson_compat = "@meson_compat@" muon-0.2.0/include/000077500000000000000000000000001441402162300141075ustar00rootroot00000000000000muon-0.2.0/include/args.h000066400000000000000000000024211441402162300152130ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ARGS_H #define MUON_ARGS_H #include "lang/workspace.h" struct args { const char **args; uint32_t len; }; void ninja_escape(struct workspace *wk, struct sbuf *sb, const char *str); void pkgconf_escape(struct workspace *wk, struct sbuf *sb, const char *str); void push_args(struct workspace *wk, obj arr, const struct args *args); void push_args_null_terminated(struct workspace *wk, obj arr, char *const *argv); obj join_args_plain(struct workspace *wk, obj arr); obj join_args_shell(struct workspace *wk, obj arr); obj join_args_ninja(struct workspace *wk, obj arr); obj join_args_shell_ninja(struct workspace *wk, obj arr); obj join_args_pkgconf(struct workspace *wk, obj arr); enum arr_to_args_flags { arr_to_args_build_target = 1 << 0, arr_to_args_custom_target = 1 << 1, arr_to_args_external_program = 1 << 2, arr_to_args_alias_target = 1 << 3, arr_to_args_relativize_paths = 1 << 4, }; bool arr_to_args(struct workspace *wk, enum arr_to_args_flags mode, obj arr, obj *res); void join_args_argstr(struct workspace *wk, const char **res, uint32_t *argc, obj arr); void env_to_envstr(struct workspace *wk, const char **res, uint32_t *envc, obj val); #endif muon-0.2.0/include/backend/000077500000000000000000000000001441402162300154765ustar00rootroot00000000000000muon-0.2.0/include/backend/backend.h000066400000000000000000000003731441402162300172410ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_BACKEND_H #define MUON_BACKEND_BACKEND_H #include "lang/workspace.h" bool backend_output(struct workspace *wk); #endif muon-0.2.0/include/backend/common_args.h000066400000000000000000000031331441402162300201530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_COMMON_ARGS_H #define MUON_BACKEND_COMMON_ARGS_H #include "lang/workspace.h" void get_std_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang, enum compiler_type t); void get_option_compile_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang); void get_option_link_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang); bool setup_compiler_args(struct workspace *wk, const struct obj_build_target *tgt, const struct project *proj, obj include_dirs, obj dep_args, obj *joined_args); bool build_target_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj *joined_args); struct setup_linker_args_ctx { enum linker_type linker; enum compiler_language link_lang; struct build_dep *args; }; void setup_linker_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, struct setup_linker_args_ctx *ctx); void setup_compiler_args_includes(struct workspace *wk, obj compiler, obj include_dirs, obj args, bool relativize); void relativize_paths(struct workspace *wk, obj arr, bool relativize_strings, obj *res); void relativize_path(struct workspace *wk, obj path, bool relativize_strings, obj *res); void relativize_path_push(struct workspace *wk, obj path, obj arr); #endif muon-0.2.0/include/backend/ninja.h000066400000000000000000000006511441402162300167500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_H #define MUON_BACKEND_NINJA_H #include "lang/workspace.h" struct write_tgt_ctx { FILE *out; const struct project *proj; bool wrote_default; }; bool ninja_write_all(struct workspace *wk); int ninja_run(struct workspace *wk, obj args, const char *chdir, const char *capture); #endif muon-0.2.0/include/backend/ninja/000077500000000000000000000000001441402162300165755ustar00rootroot00000000000000muon-0.2.0/include/backend/ninja/alias_target.h000066400000000000000000000006121441402162300214040ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: dffdff2423 * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_ALIAS_TARGET_H #define MUON_BACKEND_NINJA_ALIAS_TARGET_H #include #include "lang/types.h" struct workspace; struct project; struct write_tgt_ctx; bool ninja_write_alias_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-0.2.0/include/backend/ninja/build_target.h000066400000000000000000000005251441402162300214150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_BUILD_TARGET_H #define MUON_BACKEND_NINJA_BUILD_TARGET_H #include "lang/workspace.h" struct write_tgt_ctx; bool ninja_write_build_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-0.2.0/include/backend/ninja/custom_target.h000066400000000000000000000005301441402162300216240ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_CUSTOM_TARGET_H #define MUON_BACKEND_NINJA_CUSTOM_TARGET_H #include "lang/workspace.h" struct write_tgt_ctx; bool ninja_write_custom_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx); #endif muon-0.2.0/include/backend/ninja/rules.h000066400000000000000000000005251441402162300201020ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_NINJA_RULES_H #define MUON_BACKEND_NINJA_RULES_H #include "lang/workspace.h" bool ninja_write_rules(FILE *out, struct workspace *wk, struct project *main_proj, bool need_phony, obj compiler_rule_arr); #endif muon-0.2.0/include/backend/output.h000066400000000000000000000011511441402162300172050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BACKEND_OUTPUT_H #define MUON_BACKEND_OUTPUT_H #include "lang/workspace.h" struct output_path { const char *private_dir, *summary, *tests, *install, *compiler_check_cache, *option_info; }; extern const struct output_path output_path; typedef bool ((with_open_callback)(struct workspace *wk, void *ctx, FILE *out)); FILE *output_open(const char *dir, const char *name); bool with_open(const char *dir, const char *name, struct workspace *wk, void *ctx, with_open_callback cb); #endif muon-0.2.0/include/buf_size.h000066400000000000000000000006611441402162300160710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_BUF_SIZE_H #define MUON_BUF_SIZE_H #define BUF_SIZE_S 255 #define BUF_SIZE_1k 1024 #define BUF_SIZE_2k 2048 #define BUF_SIZE_4k 4096 #define BUF_SIZE_16k (BUF_SIZE_1k * 16) #define BUF_SIZE_32k (BUF_SIZE_1k * 32) #define BUF_SIZE_1m 1048576ul #define ARRAY_LEN(array) (sizeof(array) / sizeof(*array)) #endif muon-0.2.0/include/cmd_install.h000066400000000000000000000004351441402162300165530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_CMD_INSTALL_H #define MUON_CMD_INSTALL_H #include struct install_options { bool dry_run; }; bool install_run(struct install_options *opts); #endif muon-0.2.0/include/cmd_test.h000066400000000000000000000012771441402162300160710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_CMD_TEST_H #define MUON_CMD_TEST_H #include #include #include "lang/object.h" enum test_flag { test_flag_should_fail = 1 << 0, }; #define MAX_CMDLINE_TEST_SUITES 64 enum test_display { test_display_auto, test_display_dots, test_display_bar, }; struct test_options { const char *suites[MAX_CMDLINE_TEST_SUITES]; const char *setup; uint32_t suites_len, jobs, verbosity; enum test_display display; bool fail_fast, print_summary, no_rebuild, list; enum test_category cat; }; bool tests_run(struct test_options *opts, const char *argv0); #endif muon-0.2.0/include/coerce.h000066400000000000000000000030011441402162300155120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_COERCE_H #define MUON_COERCE_H #include "functions/common.h" enum requirement_type { requirement_skip, requirement_required, requirement_auto, }; bool coerce_environment_from_kwarg(struct workspace *wk, struct args_kw *kw, bool set_subdir, obj *res); bool coerce_key_value_dict(struct workspace *wk, uint32_t err_node, obj val, obj *res); bool coerce_include_type(struct workspace *wk, const struct str *str, uint32_t err_node, enum include_type *res); bool coerce_string_to_file(struct workspace *wk, const char *dir, obj string, obj *res); bool coerce_string(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_string_array(struct workspace *wk, uint32_t node, obj arr, obj *res); bool coerce_num_to_string(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_executable(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_requirement(struct workspace *wk, struct args_kw *kw_required, enum requirement_type *requirement); bool coerce_files(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_file(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_dirs(struct workspace *wk, uint32_t node, obj val, obj *res); bool coerce_output_files(struct workspace *wk, uint32_t node, obj val, const char *output_dir, obj *res); bool coerce_include_dirs(struct workspace *wk, uint32_t node, obj val, bool is_system, obj *res); #endif muon-0.2.0/include/compat.h000066400000000000000000000005731441402162300155500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #if !defined(_WIN32) #if !defined(_POSIX_C_SOURCE) #define _POSIX_C_SOURCE 200809L #endif #endif #if defined(__GNUC__) #define MUON_ATTR_FORMAT(type, start, end) __attribute__ ((format(type, start, end))) #else #define MUON_ATTR_FORMAT(type, start, end) #endif muon-0.2.0/include/compilers.h000066400000000000000000000102641441402162300162600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Owen Rafferty * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_COMPILERS_H #define MUON_COMPILERS_H #include #include #include "lang/types.h" struct workspace; enum compiler_type { compiler_posix, compiler_gcc, compiler_clang, compiler_apple_clang, compiler_clang_llvm_ir, compiler_nasm, compiler_yasm, compiler_type_count, }; enum linker_type { linker_posix, linker_gcc, linker_apple, linker_type_count, }; enum compiler_language { compiler_language_null, compiler_language_c, compiler_language_c_hdr, compiler_language_cpp, compiler_language_cpp_hdr, compiler_language_c_obj, compiler_language_objc, compiler_language_assembly, compiler_language_llvm_ir, compiler_language_nasm, compiler_language_count, }; enum compiler_deps_type { compiler_deps_none, compiler_deps_gcc, compiler_deps_msvc, }; enum compiler_optimization_lvl { compiler_optimization_lvl_none, compiler_optimization_lvl_0, compiler_optimization_lvl_1, compiler_optimization_lvl_2, compiler_optimization_lvl_3, compiler_optimization_lvl_g, compiler_optimization_lvl_s, }; enum compiler_pgo_stage { compiler_pgo_generate, compiler_pgo_use, }; enum compiler_warning_lvl { compiler_warning_lvl_0, compiler_warning_lvl_1, compiler_warning_lvl_2, compiler_warning_lvl_3, }; enum compiler_visibility_type { compiler_visibility_default, compiler_visibility_hidden, compiler_visibility_internal, compiler_visibility_protected, compiler_visibility_inlineshidden, }; typedef const struct args *((*compiler_get_arg_func_0)(void)); typedef const struct args *((*compiler_get_arg_func_1i)(uint32_t)); typedef const struct args *((*compiler_get_arg_func_1s)(const char *)); typedef const struct args *((*compiler_get_arg_func_2s)(const char *, const char *)); struct compiler { struct { compiler_get_arg_func_2s deps; compiler_get_arg_func_0 compile_only; compiler_get_arg_func_0 preprocess_only; compiler_get_arg_func_1s output; compiler_get_arg_func_1i optimization; compiler_get_arg_func_0 debug; compiler_get_arg_func_1i warning_lvl; compiler_get_arg_func_0 werror; compiler_get_arg_func_1s set_std; compiler_get_arg_func_1s include; compiler_get_arg_func_1s include_system; compiler_get_arg_func_1i pgo; compiler_get_arg_func_0 pic; compiler_get_arg_func_0 pie; compiler_get_arg_func_1s sanitize; compiler_get_arg_func_1s define; compiler_get_arg_func_1i visibility; compiler_get_arg_func_1s specify_lang; compiler_get_arg_func_1s color_output; compiler_get_arg_func_0 enable_lto; } args; enum compiler_deps_type deps; enum linker_type linker; }; struct linker { struct { compiler_get_arg_func_1s lib; compiler_get_arg_func_0 as_needed; compiler_get_arg_func_0 no_undefined; compiler_get_arg_func_0 start_group; compiler_get_arg_func_0 end_group; compiler_get_arg_func_0 shared; compiler_get_arg_func_1s soname; compiler_get_arg_func_1s rpath; compiler_get_arg_func_1i pgo; compiler_get_arg_func_1s sanitize; compiler_get_arg_func_0 allow_shlib_undefined; compiler_get_arg_func_0 export_dynamic; compiler_get_arg_func_0 fatal_warnings; compiler_get_arg_func_0 whole_archive; compiler_get_arg_func_0 no_whole_archive; compiler_get_arg_func_0 enable_lto; } args; }; struct language { bool is_header; bool is_linkable; }; extern struct compiler compilers[]; extern struct linker linkers[]; extern const struct language languages[]; const char *compiler_type_to_s(enum compiler_type t); const char *linker_type_to_s(enum linker_type t); const char *compiler_language_to_s(enum compiler_language l); bool s_to_compiler_language(const char *s, enum compiler_language *l); bool filename_to_compiler_language(const char *str, enum compiler_language *l); const char *compiler_language_extension(enum compiler_language l); enum compiler_language coalesce_link_languages(enum compiler_language cur, enum compiler_language new); bool compiler_detect(struct workspace *wk, obj *comp, enum compiler_language lang); void compilers_init(void); const char *ar_arguments(void); #endif muon-0.2.0/include/data/000077500000000000000000000000001441402162300150205ustar00rootroot00000000000000muon-0.2.0/include/data/bucket_array.h000066400000000000000000000023561441402162300176520ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_BUCKET_ARRAY_H #define MUON_DATA_BUCKET_ARRAY_H #include "darr.h" struct bucket_array_save { uint32_t tail_bucket, tail_bucket_len; }; struct bucket_array { struct darr buckets; uint32_t item_size; uint32_t bucket_size; uint32_t len, tail_bucket; }; struct bucket { uint8_t *mem; uint32_t len; }; void init_bucket(struct bucket_array *ba, struct bucket *b); uint64_t bucket_array_size(struct bucket_array *ba); void bucket_array_init(struct bucket_array *ba, uint32_t bucket_size, uint32_t item_size); void *bucket_array_push(struct bucket_array *ba, const void *item); void *bucket_array_pushn(struct bucket_array *ba, const void *data, uint32_t data_len, uint32_t reserve); void *bucket_array_get(const struct bucket_array *ba, uint32_t i); void bucket_array_clear(struct bucket_array *ba); void bucket_array_save(const struct bucket_array *ba, struct bucket_array_save *save); void bucket_array_restore(struct bucket_array *ba, const struct bucket_array_save *save); void bucket_array_destroy(struct bucket_array *ba); bool bucket_array_lookup_pointer(struct bucket_array *ba, const uint8_t *p, uint64_t *ret); #endif muon-0.2.0/include/data/darr.h000066400000000000000000000014001441402162300161140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_DARR_H #define MUON_DATA_DARR_H #include #include #include struct darr { size_t len; size_t cap; size_t item_size; uint8_t *e; }; void darr_init(struct darr *da, size_t initial, size_t item_size); void darr_destroy(struct darr *da); size_t darr_push(struct darr *da, const void *item); void *darr_get(const struct darr *da, size_t i); void darr_del(struct darr *da, size_t i); void darr_clear(struct darr *da); void darr_grow_by(struct darr *da, size_t size); typedef int32_t (*sort_func)(const void *a, const void *b, void *ctx); void darr_sort(struct darr *da, void *ctx, sort_func func); #endif muon-0.2.0/include/data/hash.h000066400000000000000000000025041441402162300161150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_DATA_HASH_H #define MUON_DATA_HASH_H #include #include #include "darr.h" #include "iterator.h" struct hash; typedef bool ((*hash_keycmp)(const struct hash *h, const void *a, const void *b)); typedef uint64_t ((*hash_func)(const struct hash *h, const void *k)); struct hash { struct darr meta, e, keys; size_t cap, len, load, max_load, capm; hash_keycmp keycmp; hash_func hash_func; }; typedef enum iteration_result ((*hash_with_keys_iterator_func)(void *ctx, const void *key, uint64_t val)); void hash_init(struct hash *h, size_t cap, uint32_t keysize); void hash_init_str(struct hash *h, size_t cap); void hash_destroy(struct hash *h); uint64_t *hash_get(const struct hash *h, const void *key); uint64_t *hash_get_str(const struct hash *h, const char *key); void hash_set(struct hash *h, const void *key, uint64_t val); void hash_set_str(struct hash *h, const char *key, uint64_t val); void hash_unset(struct hash *h, const void *key); void hash_unset_str(struct hash *h, const char *key); void hash_clear(struct hash *h); void hash_for_each(struct hash *h, void *ctx, iterator_func ifnc); void hash_for_each_with_keys(struct hash *h, void *ctx, hash_with_keys_iterator_func ifnc); #endif muon-0.2.0/include/embedded.h000066400000000000000000000003211441402162300160050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EMBEDDED_H #define MUON_EMBEDDED_H const char *embedded_get(const char *name); #endif muon-0.2.0/include/error.h000066400000000000000000000026641441402162300154210ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ERROR_H #define MUON_ERROR_H #include "compat.h" #include #include #include "log.h" #include "platform/filesystem.h" #define UNREACHABLE assert(false && "unreachable") #define UNREACHABLE_RETURN do { assert(false && "unreachable"); return 0; } while (0) enum error_diagnostic_store_replay_opts { error_diagnostic_store_replay_errors_only = 1 << 0, error_diagnostic_store_replay_include_sources = 1 << 1, error_diagnostic_store_replay_werror = 1 << 2, }; void error_unrecoverable(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void error_message(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *msg); void error_messagev(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *fmt, va_list args); void error_messagef(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 5, 6); void error_diagnostic_store_init(void); void error_diagnostic_store_replay(enum error_diagnostic_store_replay_opts opts, bool *saw_error); void error_diagnostic_store_push(uint32_t src_idx, uint32_t line, uint32_t col, enum log_level lvl, const char *msg); uint32_t error_diagnostic_store_push_src(struct source *src); void list_line_range(struct source *src, uint32_t lno, uint32_t list_amt); #endif muon-0.2.0/include/external/000077500000000000000000000000001441402162300157315ustar00rootroot00000000000000muon-0.2.0/include/external/bestline.h000066400000000000000000000005441441402162300177120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_READLINE_H #define MUON_EXTERNAL_READLINE_H char *muon_bestline(const char *prompt); void muon_bestline_free(const char *line); int muon_bestline_history_add(const char *line); void muon_bestline_history_free(void); #endif muon-0.2.0/include/external/libarchive.h000066400000000000000000000005321441402162300202120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBARCHIVE_H #define MUON_EXTERNAL_LIBARCHIVE_H #include #include extern const bool have_libarchive; bool muon_archive_extract(const char *buf, size_t size, const char *dest_path); #endif muon-0.2.0/include/external/libcurl.h000066400000000000000000000005761441402162300175460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBCURL_H #define MUON_EXTERNAL_LIBCURL_H #include #include extern const bool have_libcurl; void muon_curl_init(void); void muon_curl_deinit(void); bool muon_curl_fetch(const char *url, uint8_t **buf, uint64_t *len); #endif muon-0.2.0/include/external/libpkgconf.h000066400000000000000000000011411441402162300202150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_LIBPKGCONF_H #define MUON_EXTERNAL_LIBPKGCONF_H #include "lang/workspace.h" #define MAX_VERSION_LEN 32 struct pkgconf_info { char version[MAX_VERSION_LEN + 1]; obj includes, libs, not_found_libs, link_args, compile_args; }; extern const bool have_libpkgconf; bool muon_pkgconf_lookup(struct workspace *wk, obj name, bool is_static, struct pkgconf_info *info); bool muon_pkgconf_get_variable(struct workspace *wk, const char *pkg_name, const char *var, obj *res); #endif muon-0.2.0/include/external/samurai.h000066400000000000000000000004641441402162300175470ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_EXTERNAL_SAMURAI_H #define MUON_EXTERNAL_SAMURAI_H #include #include extern const bool have_samurai; bool muon_samu(uint32_t argc, char *const argv[]); #endif muon-0.2.0/include/formats/000077500000000000000000000000001441402162300155625ustar00rootroot00000000000000muon-0.2.0/include/formats/ini.h000066400000000000000000000012571441402162300165170ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_INI_H #define MUON_FORMATS_INI_H #include #include #include #include "iterator.h" #include "platform/filesystem.h" typedef bool ((*inihcb)(void *ctx, struct source *src, const char *sect, const char *k, const char *v, uint32_t line)); bool ini_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx); bool ini_reparse(const char *path, const struct source *src, char *buf, inihcb cb, void *octx); bool keyval_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx); #endif muon-0.2.0/include/formats/lines.h000066400000000000000000000006251441402162300170500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_LINES_H #define MUON_FORMATS_LINES_H #include #include #include "iterator.h" typedef enum iteration_result ((*each_line_callback)(void *ctx, char *line, size_t len)); void each_line(char *buf, uint64_t len, void *ctx, each_line_callback cb); #endif muon-0.2.0/include/formats/tap.h000066400000000000000000000005601441402162300165200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FORMATS_TAP_H #define MUON_FORMATS_TAP_H #include #include struct tap_parse_result { uint32_t total, pass, fail, skip; bool all_ok; }; void tap_parse(char *buf, uint64_t buf_len, struct tap_parse_result *res); #endif muon-0.2.0/include/functions/000077500000000000000000000000001441402162300161175ustar00rootroot00000000000000muon-0.2.0/include/functions/array.h000066400000000000000000000005051441402162300174060ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_ARRAY_H #define MUON_FUNCTIONS_ARRAY_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_array[]; extern const struct func_impl_name impl_tbl_array_internal[]; #endif muon-0.2.0/include/functions/boolean.h000066400000000000000000000004151441402162300177070ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_BOOLEAN_H #define MUON_FUNCTIONS_BOOLEAN_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_boolean[]; #endif muon-0.2.0/include/functions/both_libs.h000066400000000000000000000004501441402162300202340ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_BOTH_LIBS_H #define FUNCTIONS_BOTH_LIBS_H #include "functions/common.h" void both_libs_build_impl_tbl(void); extern struct func_impl_name impl_tbl_both_libs[]; #endif muon-0.2.0/include/functions/build_target.h000066400000000000000000000010311441402162300207300ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_BUILD_TARGET_H #define MUON_FUNCTIONS_BUILD_TARGET_H #include "functions/common.h" bool tgt_src_to_object_path(struct workspace *wk, const struct obj_build_target *tgt, obj src_file, bool relative, struct sbuf *res); bool build_target_extract_all_objects(struct workspace *wk, uint32_t err_node, obj rcvr, obj *res, bool recursive); extern const struct func_impl_name impl_tbl_build_target[8]; #endif muon-0.2.0/include/functions/common.h000066400000000000000000000030251441402162300175600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_COMMON_H #define MUON_FUNCTIONS_COMMON_H #include "lang/parser.h" #include "lang/workspace.h" typedef bool (*func_impl)(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res); typedef obj (*func_impl_rcvr_transform)(struct workspace *wk, obj rcvr); struct func_impl_name { const char *name; func_impl func; type_tag return_type; bool pure, fuzz_unsafe, extension; func_impl_rcvr_transform rcvr_transform; }; extern const struct func_impl_name *kernel_func_tbl[language_mode_count]; extern const struct func_impl_name *func_tbl[obj_type_count][language_mode_count]; struct args_norm { type_tag type; obj val, node; bool set; }; struct args_kw { const char *key; type_tag type; obj val, node; bool set; bool required; }; extern bool disabler_among_args_immunity, disable_fuzz_unsafe_functions; void build_func_impl_tables(void); const struct func_impl_name *func_lookup(const struct func_impl_name *impl_tbl, const char *name); bool interp_args(struct workspace *wk, uint32_t args_node, struct args_norm positional_args[], struct args_norm optional_positional_args[], struct args_kw keyword_args[]); bool builtin_run(struct workspace *wk, bool have_rcvr, obj rcvr_id, uint32_t node_id, obj *res); bool analyze_function(struct workspace *wk, const struct func_impl_name *fi, uint32_t args_node, obj rcvr, obj *res, bool *was_pure); void dump_function_signatures(struct workspace *wk); #endif muon-0.2.0/include/functions/compiler.h000066400000000000000000000004201441402162300200760ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_COMPILER_H #define MUON_FUNCTIONS_COMPILER_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_compiler[]; #endif muon-0.2.0/include/functions/configuration_data.h000066400000000000000000000004561441402162300221350ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_CONFIGURATION_DATA_H #define MUON_FUNCTIONS_CONFIGURATION_DATA_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_configuration_data[]; #endif muon-0.2.0/include/functions/custom_target.h000066400000000000000000000005361441402162300211540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_CUSTOM_TARGET_H #define MUON_FUNCTIONS_CUSTOM_TARGET_H #include "functions/common.h" bool custom_target_is_linkable(struct workspace *wk, obj ct); extern const struct func_impl_name impl_tbl_custom_target[]; #endif muon-0.2.0/include/functions/dependency.h000066400000000000000000000004261441402162300204100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DEPENDENCY_H #define MUON_FUNCTIONS_DEPENDENCY_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_dependency[]; #endif muon-0.2.0/include/functions/dict.h000066400000000000000000000004041441402162300172110ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DICT_H #define MUON_FUNCTIONS_DICT_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_dict[]; #endif muon-0.2.0/include/functions/disabler.h000066400000000000000000000004201441402162300200510ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_DISABLER_H #define MUON_FUNCTIONS_DISABLER_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_disabler[]; #endif muon-0.2.0/include/functions/environment.h000066400000000000000000000012301441402162300206300ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_ENVIRONMENT_H #define MUON_FUNCTIONS_ENVIRONMENT_H #include "functions/common.h" enum environment_set_mode { environment_set_mode_set, environment_set_mode_append, environment_set_mode_prepend, }; bool environment_set(struct workspace *wk, obj env, enum environment_set_mode mode, obj key, obj vals, obj sep); bool environment_to_dict(struct workspace *wk, obj env, obj *res); void set_default_environment_vars(struct workspace *wk, obj env, bool set_subdir); extern const struct func_impl_name impl_tbl_environment[]; #endif muon-0.2.0/include/functions/external_program.h000066400000000000000000000005741441402162300216470ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_EXTERNAL_PROGRAM_H #define MUON_FUNCTIONS_EXTERNAL_PROGRAM_H #include "functions/common.h" void find_program_guess_version(struct workspace *wk, const char *path, obj *ver); extern const struct func_impl_name impl_tbl_external_program[]; #endif muon-0.2.0/include/functions/feature_opt.h000066400000000000000000000004311441402162300206030ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_FEATURE_OPT_H #define MUON_FUNCTIONS_FEATURE_OPT_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_feature_opt[]; #endif muon-0.2.0/include/functions/file.h000066400000000000000000000004741441402162300172140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_FILE_H #define MUON_FUNCTIONS_FILE_H #include "functions/common.h" bool file_is_linkable(struct workspace *wk, obj file); extern const struct func_impl_name impl_tbl_file[]; #endif muon-0.2.0/include/functions/generator.h000066400000000000000000000006231441402162300202570ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_GENERATOR_H #define MUON_FUNCTIONS_GENERATOR_H #include "functions/common.h" bool generated_list_process_for_target(struct workspace *wk, uint32_t err_node, obj gl, obj tgt, bool add_targets, obj *res); extern const struct func_impl_name impl_tbl_generator[]; #endif muon-0.2.0/include/functions/kernel.h000066400000000000000000000010301441402162300175420ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_H #define MUON_FUNCTIONS_KERNEL_H #include "functions/common.h" struct range_params { uint32_t start, stop, step; }; bool func_range_common(struct workspace *wk, uint32_t args_node, struct range_params *res); extern const struct func_impl_name impl_tbl_kernel[]; extern const struct func_impl_name impl_tbl_kernel_internal[]; extern const struct func_impl_name impl_tbl_kernel_opts[]; #endif muon-0.2.0/include/functions/kernel/000077500000000000000000000000001441402162300173775ustar00rootroot00000000000000muon-0.2.0/include/functions/kernel/build_target.h000066400000000000000000000014631441402162300222210ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_BUILD_TARGET_H #define MUON_FUNCTIONS_KERNEL_BUILD_TARGET_H #include "functions/common.h" bool func_both_libraries(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_build_target(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_executable(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_library(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_shared_library(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_static_library(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_shared_module(struct workspace *wk, obj _, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/kernel/configure_file.h000066400000000000000000000005071441402162300225320ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_CONFIGURE_FILE_H #define MUON_FUNCTIONS_KERNEL_CONFIGURE_FILE_H #include "functions/common.h" bool func_configure_file(struct workspace *wk, obj _, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/kernel/custom_target.h000066400000000000000000000022401441402162300224260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_CUSTOM_TARGET_H #define MUON_FUNCTIONS_KERNEL_CUSTOM_TARGET_H #include "functions/common.h" struct make_custom_target_opts { obj name; uint32_t input_node; uint32_t output_node; uint32_t command_node; obj input_orig; obj output_orig; const char *output_dir, *build_dir; obj command_orig; obj depfile_orig; obj extra_args; bool capture; bool feed; bool extra_args_valid, extra_args_used; }; bool make_custom_target(struct workspace *wk, struct make_custom_target_opts *opts, obj *res); struct process_custom_target_commandline_opts { uint32_t err_node; bool relativize; obj name; obj input; obj output; obj depfile; obj depends; obj extra_args; const char *build_dir; bool extra_args_valid, extra_args_used; }; bool process_custom_target_commandline(struct workspace *wk, struct process_custom_target_commandline_opts *opts, obj arr, obj *res); bool func_custom_target(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_vcs_tag(struct workspace *wk, obj _, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/kernel/dependency.h000066400000000000000000000015331441402162300216700ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_DEPENDENCY_H #define MUON_FUNCTIONS_KERNEL_DEPENDENCY_H #include "functions/common.h" void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest); bool dep_process_link_with(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest); bool dep_process_link_whole(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest); void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, obj dest); void build_dep_init(struct workspace *wk, struct build_dep *dep); bool func_dependency(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res); bool func_declare_dependency(struct workspace *wk, obj _, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/kernel/install.h000066400000000000000000000013411441402162300212150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_INSTALL_H #define MUON_FUNCTIONS_KERNEL_INSTALL_H #include "functions/common.h" bool func_install_subdir(struct workspace *wk, obj _, uint32_t args_node, obj *ret); bool func_install_man(struct workspace *wk, obj _, uint32_t args_node, obj *ret); bool func_install_symlink(struct workspace *wk, obj _, uint32_t args_node, obj *ret); bool func_install_emptydir(struct workspace *wk, obj _, uint32_t args_node, obj *ret); bool func_install_data(struct workspace *wk, obj _, uint32_t args_node, obj *res); bool func_install_headers(struct workspace *wk, obj _, uint32_t args_node, obj *ret); #endif muon-0.2.0/include/functions/kernel/options.h000066400000000000000000000006071441402162300212460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_OPTIONS_H #define MUON_FUNCTIONS_KERNEL_OPTIONS_H #include "lang/workspace.h" bool func_option(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res); bool func_get_option(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/kernel/subproject.h000066400000000000000000000007761441402162300217420ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_KERNEL_SUBPROJECT_H #define MUON_FUNCTIONS_KERNEL_SUBPROJECT_H #include "coerce.h" #include "functions/common.h" #include "lang/workspace.h" bool subproject(struct workspace *wk, obj name, enum requirement_type req, struct args_kw *default_options, struct args_kw *versions, obj *res); bool func_subproject(struct workspace *wk, obj _, uint32_t args_node, obj *res); #endif muon-0.2.0/include/functions/machine.h000066400000000000000000000013501441402162300176730ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MACHINE_H #define MUON_FUNCTIONS_MACHINE_H #include "functions/common.h" enum machine_system { machine_system_dragonfly, machine_system_freebsd, machine_system_gnu, machine_system_haiku, machine_system_linux, machine_system_netbsd, machine_system_openbsd, machine_system_sunos, machine_system_android, machine_system_emscripten, machine_system_windows, machine_system_cygwin, machine_system_msys2, machine_system_darwin, machine_system_unknown, }; enum machine_system machine_system(void); uint32_t machine_cpu_address_bits(void); extern const struct func_impl_name impl_tbl_machine[]; #endif muon-0.2.0/include/functions/meson.h000066400000000000000000000004071441402162300174120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MESON_H #define MUON_FUNCTIONS_MESON_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_meson[]; #endif muon-0.2.0/include/functions/modules.h000066400000000000000000000011071441402162300177370ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_H #define MUON_FUNCTIONS_MODULES_H #include "functions/common.h" extern const char *module_names[module_count]; extern const struct func_impl_name impl_tbl_module[]; extern const struct func_impl_name *module_func_tbl[module_count][language_mode_count]; bool module_lookup(const char *name, enum module *res, bool *has_impl); const struct func_impl_name *module_func_lookup(struct workspace *wk, const char *name, enum module mod); #endif muon-0.2.0/include/functions/modules/000077500000000000000000000000001441402162300175675ustar00rootroot00000000000000muon-0.2.0/include/functions/modules/fs.h000066400000000000000000000005271441402162300203540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_FS_H #define MUON_FUNCTIONS_MODULES_FS_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_module_fs[]; extern const struct func_impl_name impl_tbl_module_fs_internal[]; #endif muon-0.2.0/include/functions/modules/keyval.h000066400000000000000000000004411441402162300212320ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_KEYVAL_H #define MUON_FUNCTIONS_MODULES_KEYVAL_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_module_keyval[]; #endif muon-0.2.0/include/functions/modules/pkgconfig.h000066400000000000000000000004521441402162300217100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_PKGCONFIG_H #define MUON_FUNCTIONS_MODULES_PKGCONFIG_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_module_pkgconfig[]; #endif muon-0.2.0/include/functions/modules/python.h000066400000000000000000000005371441402162300212660ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_PYTHON_H #define MUON_FUNCTIONS_MODULES_PYTHON_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_module_python[]; extern const struct func_impl_name impl_tbl_module_python3[]; #endif muon-0.2.0/include/functions/modules/sourceset.h000066400000000000000000000004521441402162300217550ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_MODULES_SOURCESET_H #define MUON_FUNCTIONS_MODULES_SOURCESET_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_module_sourceset[]; #endif muon-0.2.0/include/functions/number.h000066400000000000000000000004121441402162300175550ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_NUMBER_H #define MUON_FUNCTIONS_NUMBER_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_number[]; #endif muon-0.2.0/include/functions/run_result.h000066400000000000000000000004261441402162300204740ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_RUN_RESULT_H #define MUON_FUNCTIONS_RUN_RESULT_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_run_result[]; #endif muon-0.2.0/include/functions/source_configuration.h000066400000000000000000000004521441402162300225200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_SOURCE_CONFIGURATION_H #define FUNCTIONS_SOURCE_CONFIGURATION_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_source_configuration[]; #endif muon-0.2.0/include/functions/source_set.h000066400000000000000000000004141441402162300204420ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_SOURCE_SET_H #define FUNCTIONS_SOURCE_SET_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_source_set[]; #endif muon-0.2.0/include/functions/string.h000066400000000000000000000013361441402162300176010ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_STRING_H #define MUON_FUNCTIONS_STRING_H #include "functions/common.h" enum format_cb_result { format_cb_found, format_cb_not_found, format_cb_error, format_cb_skip, }; bool version_compare(struct workspace *wk, uint32_t err_node, const struct str *ver, obj cmp_arr, bool *res); typedef enum format_cb_result ((*string_format_cb)(struct workspace *wk, uint32_t node, void *ctx, const struct str *key, uint32_t *elem)); bool string_format(struct workspace *wk, uint32_t err_node, obj str, obj *res, void *ctx, string_format_cb cb); extern const struct func_impl_name impl_tbl_string[]; #endif muon-0.2.0/include/functions/subproject.h000066400000000000000000000006151441402162300204520ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_FUNCTIONS_SUBPROJECT_H #define MUON_FUNCTIONS_SUBPROJECT_H #include "functions/common.h" bool subproject_get_variable(struct workspace *wk, uint32_t node, obj name_id, obj fallback, obj subproj, obj *res); extern const struct func_impl_name impl_tbl_subproject[]; #endif muon-0.2.0/include/functions/template.h000066400000000000000000000003671441402162300201110ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef FUNCTIONS_xxx_H #define FUNCTIONS_xxx_H #include "functions/common.h" extern const struct func_impl_name impl_tbl_xxx[]; #endif muon-0.2.0/include/guess.h000066400000000000000000000004011441402162300154010ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_GUESS_H #define MUON_GUESS_H #include "lang/workspace.h" bool guess_version(struct workspace *wk, const char *src, obj *res); #endif muon-0.2.0/include/install.h000066400000000000000000000010221441402162300157210ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_INSTALL_H #define MUON_INSTALL_H #include "lang/workspace.h" struct obj_install_target *push_install_target(struct workspace *wk, obj src, obj dest, obj mode); bool push_install_target_install_dir(struct workspace *wk, obj src, obj install_dir, obj mode); bool push_install_targets(struct workspace *wk, uint32_t err_node, obj filenames, obj install_dirs, obj install_mode, bool preserve_path); #endif muon-0.2.0/include/iterator.h000066400000000000000000000004451441402162300161140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_ITERATOR_H #define MUON_ITERATOR_H enum iteration_result { ir_err, ir_cont, ir_done, }; typedef enum iteration_result (*iterator_func)(void *ctx, void *val); #endif muon-0.2.0/include/lang/000077500000000000000000000000001441402162300150305ustar00rootroot00000000000000muon-0.2.0/include/lang/analyze.h000066400000000000000000000014521441402162300166460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_ANALYZE_H #define MUON_LANG_ANALYZE_H #include "error.h" #include "workspace.h" enum analyze_diagnostic { analyze_diagnostic_unused_variable = 1 << 0, analyze_diagnostic_reassign_to_conflicting_type = 1 << 1, analyze_diagnostic_dead_code = 1 << 2, }; struct analyze_opts { bool subdir_error; enum error_diagnostic_store_replay_opts replay_opts; const char *file_override, *internal_file; uint64_t enabled_diagnostics; }; bool analyze_diagnostic_name_to_enum(const char *name, enum analyze_diagnostic *ret); void analyze_print_diagnostic_names(void); void analyze_check_dead_code(struct workspace *wk, struct ast *ast); bool do_analyze(struct analyze_opts *opts); #endif muon-0.2.0/include/lang/eval.h000066400000000000000000000020771441402162300161360ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_EVAL_H #define MUON_LANG_EVAL_H #include #include #include #include "lang/types.h" struct workspace; struct source; struct darr; enum language_mode { language_external, language_internal, language_opts, language_mode_count, }; enum eval_mode { eval_mode_default, eval_mode_repl, eval_mode_first, }; struct source_data { char *data; uint64_t data_len; }; void source_data_destroy(struct source_data *sdata); bool eval_project(struct workspace *wk, const char *subproject_name, const char *cwd, const char *build_dir, uint32_t *proj_id); bool eval_project_file(struct workspace *wk, const char *path, bool first); bool eval(struct workspace *wk, struct source *src, enum eval_mode mode, obj *res); bool eval_str(struct workspace *wk, const char *str, enum eval_mode mode, obj *res); void repl(struct workspace *wk, bool dbg); const char *determine_project_root(struct workspace *wk, const char *path); #endif muon-0.2.0/include/lang/fmt.h000066400000000000000000000004441441402162300157710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_FMT_H #define MUON_LANG_FMT_H #include #include "lang/parser.h" bool fmt(struct source *src, FILE *out, const char *cfg_path, bool check_only); #endif muon-0.2.0/include/lang/interpreter.h000066400000000000000000000037331441402162300175520ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_INTERPRETER_H #define MUON_LANG_INTERPRETER_H #include "compat.h" #include #include #include "object.h" #include "parser.h" #include "workspace.h" bool interp_node(struct workspace *wk, uint32_t n_id, obj *res); bool interp_arithmetic(struct workspace *wk, uint32_t err_node, enum arithmetic_type type, bool plusassign, uint32_t nl, uint32_t nr, obj *res); bool interp_index(struct workspace *wk, struct node *n, obj l_id, bool do_chain, obj *res); bool interp_stringify(struct workspace *wk, struct node *n, obj *res); bool interp_comparison(struct workspace *wk, struct node *n, obj *res); void interp_error(struct workspace *wk, uint32_t n_id, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void interp_warning(struct workspace *wk, uint32_t n_id, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool typecheck_custom(struct workspace *wk, uint32_t n_id, obj obj_id, type_tag type, const char *fmt); obj typechecking_type_to_arr(struct workspace *wk, type_tag t); const char *typechecking_type_to_s(struct workspace *wk, type_tag t); bool typecheck_simple_err(struct workspace *wk, obj o, type_tag type); bool typecheck_array(struct workspace *wk, uint32_t n_id, obj arr, type_tag type); bool typecheck_dict(struct workspace *wk, uint32_t n_id, obj dict, type_tag type); bool typecheck(struct workspace *wk, uint32_t n_id, obj obj_id, type_tag type); bool boundscheck(struct workspace *wk, uint32_t n_id, uint32_t len, int64_t *i); bool bounds_adjust(struct workspace *wk, uint32_t len, int64_t *i); bool rangecheck(struct workspace *wk, uint32_t n_id, int64_t min, int64_t max, int64_t n); void assign_variable(struct workspace *wk, const char *name, obj o, uint32_t n_id); void unassign_variable(struct workspace *wk, const char *name); void interpreter_init(void); #endif muon-0.2.0/include/lang/lexer.h000066400000000000000000000027061441402162300163250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_LEXER_H #define MUON_LANG_LEXER_H #include #include #include "data/darr.h" #include "lang/eval.h" enum token_type { tok_eof, tok_eol, tok_lparen, tok_rparen, tok_lbrack, tok_rbrack, tok_lcurl, tok_rcurl, tok_dot, tok_comma, tok_colon, tok_question_mark, /* math */ tok_plus, tok_minus, tok_star, tok_slash, tok_modulo, /* assign */ tok_assign, tok_plus_assign, /* comparison */ tok_eq, tok_neq, tok_gt, tok_geq, tok_lt, tok_leq, /* keywords */ tok_if, tok_else, tok_elif, tok_endif, tok_and, tok_or, tok_not, tok_foreach, tok_endforeach, tok_in, tok_continue, tok_break, /* literals */ tok_identifier, tok_string, tok_number, tok_true, tok_false, /* special */ tok_stringify, /* formatting only */ tok_comment, tok_fmt_eol, }; union token_data { const char *s; int64_t n; }; struct token { union token_data dat; enum token_type type; uint32_t n, line, col; }; struct tokens { struct darr tok; }; enum lexer_mode { lexer_mode_format = 1 << 0, }; bool lexer_lex(struct tokens *toks, struct source_data *sdata, struct source *src, enum lexer_mode mode); void tokens_destroy(struct tokens *toks); const char *tok_type_to_s(enum token_type type); const char *tok_to_s(struct token *token); #endif muon-0.2.0/include/lang/object.h000066400000000000000000000420411441402162300164500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_OBJECT_H #define MUON_LANG_OBJECT_H #include "compat.h" #include #include #include "compilers.h" #include "data/bucket_array.h" #include "iterator.h" #include "lang/types.h" enum obj_type { /* singleton object types */ obj_null, obj_meson, obj_disabler, obj_machine, // this won't be a singleton object when cross compilaton is implemented /* simple object types */ obj_bool, obj_file, obj_feature_opt, /* complex object types */ _obj_aos_start, obj_number = _obj_aos_start, obj_string, obj_array, obj_dict, obj_compiler, obj_build_target, obj_custom_target, obj_subproject, obj_dependency, obj_external_program, obj_run_result, obj_configuration_data, obj_test, obj_module, obj_install_target, obj_environment, obj_include_directory, obj_option, obj_generator, obj_generated_list, obj_alias_target, obj_both_libs, obj_source_set, obj_source_configuration, obj_typeinfo, obj_type_count, }; typedef uint64_t type_tag; #define ARG_TYPE_NULL (obj_type_count + 1) #define ARG_TYPE_GLOB (((type_tag)1) << 61) #define ARG_TYPE_ARRAY_OF (((type_tag)1) << 62) #define obj_typechecking_type_tag (((type_tag)1) << 63) #define tc_meson (obj_typechecking_type_tag | (((type_tag)1) << 0)) #define tc_disabler (obj_typechecking_type_tag | (((type_tag)1) << 1)) #define tc_machine (obj_typechecking_type_tag | (((type_tag)1) << 2)) #define tc_bool (obj_typechecking_type_tag | (((type_tag)1) << 3)) #define tc_file (obj_typechecking_type_tag | (((type_tag)1) << 4)) #define tc_feature_opt (obj_typechecking_type_tag | (((type_tag)1) << 5)) #define tc_number (obj_typechecking_type_tag | (((type_tag)1) << 6)) #define tc_string (obj_typechecking_type_tag | (((type_tag)1) << 7)) #define tc_array (obj_typechecking_type_tag | (((type_tag)1) << 8)) #define tc_dict (obj_typechecking_type_tag | (((type_tag)1) << 9)) #define tc_compiler (obj_typechecking_type_tag | (((type_tag)1) << 10)) #define tc_build_target (obj_typechecking_type_tag | (((type_tag)1) << 11)) #define tc_custom_target (obj_typechecking_type_tag | (((type_tag)1) << 12)) #define tc_subproject (obj_typechecking_type_tag | (((type_tag)1) << 13)) #define tc_dependency (obj_typechecking_type_tag | (((type_tag)1) << 14)) #define tc_external_program (obj_typechecking_type_tag | (((type_tag)1) << 15)) #define tc_run_result (obj_typechecking_type_tag | (((type_tag)1) << 16)) #define tc_configuration_data (obj_typechecking_type_tag | (((type_tag)1) << 17)) #define tc_test (obj_typechecking_type_tag | (((type_tag)1) << 18)) #define tc_module (obj_typechecking_type_tag | (((type_tag)1) << 19)) #define tc_install_target (obj_typechecking_type_tag | (((type_tag)1) << 20)) #define tc_environment (obj_typechecking_type_tag | (((type_tag)1) << 21)) #define tc_include_directory (obj_typechecking_type_tag | (((type_tag)1) << 22)) #define tc_option (obj_typechecking_type_tag | (((type_tag)1) << 23)) #define tc_generator (obj_typechecking_type_tag | (((type_tag)1) << 24)) #define tc_generated_list (obj_typechecking_type_tag | (((type_tag)1) << 25)) #define tc_alias_target (obj_typechecking_type_tag | (((type_tag)1) << 26)) #define tc_both_libs (obj_typechecking_type_tag | (((type_tag)1) << 27)) #define tc_source_set (obj_typechecking_type_tag | (((type_tag)1) << 28)) #define tc_source_configuration (obj_typechecking_type_tag | (((type_tag)1) << 29)) #define tc_type_count 30 #define tc_any (tc_bool | tc_file | tc_number | tc_string | tc_array | tc_dict \ | tc_compiler | tc_build_target | tc_custom_target \ | tc_subproject | tc_dependency | tc_feature_opt \ | tc_external_program | tc_run_result \ | tc_configuration_data | tc_test | tc_module \ | tc_install_target | tc_environment | tc_include_directory \ | tc_option | tc_generator | tc_generated_list \ | tc_alias_target | tc_both_libs | tc_disabler \ | tc_meson | tc_machine | tc_source_set | tc_source_configuration) #define tc_exe (tc_string | tc_file | tc_external_program | tc_build_target | tc_custom_target | tc_both_libs) #define tc_coercible_env (tc_environment | tc_string | tc_array | tc_dict) #define tc_coercible_files (tc_string | tc_custom_target | tc_build_target | tc_file | tc_both_libs) #define tc_coercible_inc (tc_string | tc_include_directory) #define tc_command_array (ARG_TYPE_ARRAY_OF | tc_exe) #define tc_depends_kw (ARG_TYPE_ARRAY_OF | tc_build_target | tc_custom_target | tc_both_libs) #define tc_install_mode_kw (ARG_TYPE_ARRAY_OF | tc_string | tc_number | tc_bool) #define tc_required_kw (tc_bool | tc_feature_opt) /* XXX: tc_file should not really be in tc_link_with_kw, however this is * how muon represents custom_target outputs, which are valid link_with * arguments... */ #define tc_link_with_kw (ARG_TYPE_ARRAY_OF | tc_build_target | tc_custom_target | tc_file | tc_both_libs) #define tc_message (ARG_TYPE_GLOB | tc_string | tc_bool | tc_number | tc_array | tc_dict) // doesn't handle nested types struct obj_typechecking_type_to_obj_type { enum obj_type type; type_tag tc; }; /* start of object structs */ struct obj_typeinfo { type_tag type, subtype; }; enum tgt_type { tgt_executable = 1 << 0, tgt_static_library = 1 << 1, tgt_dynamic_library = 1 << 2, tgt_shared_module = 1 << 3, }; enum tgt_type_count { tgt_type_count = 4, }; // keep in sync enum feature_opt_state { feature_opt_auto, feature_opt_enabled, feature_opt_disabled, }; enum module { module_fs, module_keyval, module_pkgconfig, module_python, module_python3, module_sourceset, // unimplemented module_unimplemented_separator, module_cmake = module_unimplemented_separator, module_dlang, module_gnome, module_hotdoc, module_i18n, module_java, module_modtest, module_qt, module_qt4, module_qt5, module_qt6, module_unstable_cuda, module_unstable_external_project, module_unstable_icestorm, module_unstable_rust, module_unstable_simd, module_unstable_wayland, module_windows, module_count, }; enum str_flags { str_flag_big = 1 << 0, }; struct str { const char *s; uint32_t len; enum str_flags flags; }; struct obj_internal { enum obj_type t; uint32_t val; }; struct obj_subproject { uint32_t id; bool found; }; struct obj_module { enum module module; bool found, has_impl; }; struct obj_array { obj val; // any obj next; // obj_array obj tail; // obj_array uint32_t len; bool have_next; }; struct obj_dict { obj key; // obj_string obj val; // any obj next; // obj_array obj tail; // obj_array uint32_t len; bool have_next; }; enum build_tgt_flags { build_tgt_flag_export_dynamic = 1 << 0, build_tgt_flag_pic = 1 << 1, build_tgt_generated_include = 1 << 2, build_tgt_flag_build_by_default = 1 << 3, build_tgt_flag_visibility = 1 << 4, build_tgt_flag_installed = 1 << 5, build_tgt_flag_pie = 1 << 6, }; struct build_dep { enum compiler_language link_language; obj link_whole; // obj_array obj link_with; // obj_array obj link_with_not_found; // obj_array obj link_args; // obj_array obj compile_args; // obj_array obj include_directories; // obj_array obj sources; // obj_array obj order_deps; // obj_array obj rpath; // obj_array struct { obj deps; obj link_with; obj link_whole; } raw; }; struct obj_build_target { obj name; // obj_string obj build_name; // obj_string obj build_path; // obj_string obj private_path; // obj_string obj cwd; // obj_string obj build_dir; // obj_string obj soname; // obj_string obj src; // obj_array obj objects; // obj_array obj args; // obj_dict obj link_depends; // obj_array obj generated_pc; // obj_string obj override_options; // obj_array obj required_compilers; // obj_dict struct build_dep dep; struct build_dep dep_internal; enum compiler_visibility_type visibility; enum build_tgt_flags flags; enum tgt_type type; }; struct obj_both_libs { obj static_lib; // obj_build_target obj dynamic_lib; // obj_build_target }; enum custom_target_flags { custom_target_capture = 1 << 0, custom_target_build_always_stale = 1 << 1, custom_target_build_by_default = 1 << 2, custom_target_feed = 1 << 3, custom_target_console = 1 << 4, }; struct obj_custom_target { obj name; // obj_string obj args; // obj_array obj input; // obj_array obj output; // obj_array obj depends; // obj_array obj private_path; // obj_string obj env; // str | list[str] | dict[str] | env obj depfile; // str enum custom_target_flags flags; }; struct obj_alias_target { obj name; // obj_string obj depends; // obj_array }; enum dependency_type { dependency_type_declared, dependency_type_pkgconf, dependency_type_threads, dependency_type_external_library, dependency_type_appleframeworks, }; enum dep_flags { dep_flag_found = 1 << 0, }; enum include_type { include_type_preserve, include_type_system, include_type_non_system, }; struct obj_dependency { obj name; // obj_string obj version; // obj_string obj variables; // obj_dict struct build_dep dep; enum dep_flags flags; enum dependency_type type; enum include_type include_type; }; struct obj_external_program { bool found, guessed_ver; obj full_path; obj ver; }; enum run_result_flags { run_result_flag_from_compile = 1 << 0, run_result_flag_compile_ok = 1 << 1, }; struct obj_run_result { obj out; obj err; int32_t status; enum run_result_flags flags; }; struct obj_configuration_data { obj dict; // obj_dict }; enum test_category { test_category_test, test_category_benchmark, }; enum test_protocol { test_protocol_exitcode, test_protocol_tap, test_protocol_gtest, test_protocol_rust, }; struct obj_test { obj name; // obj_string obj exe; // obj_string obj args; // obj_array obj env; // obj_array obj suites; // obj_array obj workdir; // obj_string obj depends; // obj_array of obj_string obj timeout; // obj_number obj priority; // obj_number bool should_fail, is_parallel, verbose; enum test_category category; enum test_protocol protocol; }; struct obj_compiler { obj cmd_arr; obj ver; obj libdirs; enum compiler_type type; enum compiler_language lang; }; enum install_target_type { install_target_default, install_target_subdir, install_target_symlink, install_target_emptydir, }; struct obj_install_target { obj src; obj dest; bool has_perm; uint32_t perm; obj exclude_directories; // obj_array of obj_string obj exclude_files; // obj_array of obj_string enum install_target_type type; bool build_target; }; struct obj_environment { obj actions; // array }; struct obj_include_directory { obj path; bool is_system; }; enum build_option_type { op_string, op_boolean, op_combo, op_integer, op_array, op_feature, build_option_type_count, }; enum build_option_kind { build_option_kind_default, build_option_kind_prefixed_dir, }; enum option_value_source { option_value_source_unset, option_value_source_default, option_value_source_environment, option_value_source_yield, option_value_source_default_options, option_value_source_subproject_default_options, option_value_source_override_options, option_value_source_deprecated_rename, option_value_source_commandline, }; struct obj_option { obj name; obj val; obj choices; obj max; obj min; obj deprecated; obj description; enum option_value_source source; enum build_option_type type; enum build_option_kind kind; bool yield, builtin; }; struct obj_generator { obj output; obj raw_command; obj depfile; obj depends; bool capture; bool feed; }; struct obj_generated_list { obj generator; // obj_generator obj input; // obj_array of obj_file obj extra_arguments; // obj_array of obj_string obj preserve_path_from; // obj_string }; struct obj_source_set { obj rules; bool frozen; }; struct obj_source_configuration { obj sources, dependencies; }; /* end of object structs */ struct obj_clear_mark { uint32_t obji; struct bucket_array_save objs, chrs; struct bucket_array_save obj_aos[obj_type_count - _obj_aos_start]; }; void make_obj(struct workspace *wk, obj *id, enum obj_type type); enum obj_type get_obj_type(struct workspace *wk, obj id); type_tag obj_type_to_tc_type(enum obj_type t); void obj_set_clear_mark(struct workspace *wk, struct obj_clear_mark *mk); void obj_clear(struct workspace *wk, const struct obj_clear_mark *mk); bool get_obj_bool(struct workspace *wk, obj o); void set_obj_bool(struct workspace *wk, obj o, bool v); int64_t get_obj_number(struct workspace *wk, obj o); void set_obj_number(struct workspace *wk, obj o, int64_t v); obj *get_obj_file(struct workspace *wk, obj o); const char *get_file_path(struct workspace *wk, obj o); const struct str *get_str(struct workspace *wk, obj s); enum feature_opt_state get_obj_feature_opt(struct workspace *wk, obj fo); void set_obj_feature_opt(struct workspace *wk, obj fo, enum feature_opt_state state); #define OBJ_GETTER(type) struct type *get_ ## type(struct workspace *wk, obj o) OBJ_GETTER(obj_array); OBJ_GETTER(obj_dict); OBJ_GETTER(obj_compiler); OBJ_GETTER(obj_build_target); OBJ_GETTER(obj_custom_target); OBJ_GETTER(obj_subproject); OBJ_GETTER(obj_dependency); OBJ_GETTER(obj_external_program); OBJ_GETTER(obj_run_result); OBJ_GETTER(obj_configuration_data); OBJ_GETTER(obj_test); OBJ_GETTER(obj_module); OBJ_GETTER(obj_install_target); OBJ_GETTER(obj_environment); OBJ_GETTER(obj_include_directory); OBJ_GETTER(obj_option); OBJ_GETTER(obj_generator); OBJ_GETTER(obj_generated_list); OBJ_GETTER(obj_alias_target); OBJ_GETTER(obj_both_libs); OBJ_GETTER(obj_typeinfo); OBJ_GETTER(obj_source_set); OBJ_GETTER(obj_source_configuration); #undef OBJ_GETTER struct sbuf; const char *obj_type_to_s(enum obj_type t); void obj_to_s(struct workspace *wk, obj o, struct sbuf *sb); bool obj_equal(struct workspace *wk, obj left, obj right); bool obj_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val, obj *ret); bool obj_vasprintf(struct workspace *wk, struct sbuf *sb, const char *fmt, va_list ap); bool obj_asprintf(struct workspace *wk, struct sbuf *sb, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool obj_vfprintf(struct workspace *wk, FILE *f, const char *fmt, va_list ap) MUON_ATTR_FORMAT(printf, 3, 0); bool obj_fprintf(struct workspace *wk, FILE *f, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool obj_printf(struct workspace *wk, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); void obj_inspect(struct workspace *wk, FILE *out, obj val); typedef enum iteration_result (*obj_array_iterator)(struct workspace *wk, void *ctx, obj val); void obj_array_push(struct workspace *wk, obj arr, obj child); void obj_array_prepend(struct workspace *wk, obj *arr, obj val); bool obj_array_foreach(struct workspace *wk, obj arr, void *ctx, obj_array_iterator cb); bool obj_array_foreach_flat(struct workspace *wk, obj arr, void *usr_ctx, obj_array_iterator cb); bool obj_array_in(struct workspace *wk, obj arr, obj val); bool obj_array_index_of(struct workspace *wk, obj arr, obj val, uint32_t *idx); void obj_array_index(struct workspace *wk, obj arr, int64_t i, obj *res); void obj_array_extend(struct workspace *wk, obj arr, obj arr2); void obj_array_extend_nodup(struct workspace *wk, obj arr, obj arr2); void obj_array_dup(struct workspace *wk, obj arr, obj *res); bool obj_array_join(struct workspace *wk, bool flat, obj arr, obj join, obj *res); void obj_array_tail(struct workspace *wk, obj arr, obj *res); void obj_array_set(struct workspace *wk, obj arr, int64_t i, obj v); void obj_array_del(struct workspace *wk, obj arr, int64_t i); void obj_array_dedup(struct workspace *wk, obj arr, obj *res); bool obj_array_flatten_one(struct workspace *wk, obj val, obj *res); typedef int32_t (*obj_array_sort_func)(struct workspace *wk, void *_ctx, obj a, obj b); int32_t obj_array_sort_by_str(struct workspace *wk, void *_ctx, obj a, obj b); void obj_array_sort(struct workspace *wk, void *usr_ctx, obj arr, obj_array_sort_func func, obj *res); typedef enum iteration_result (*obj_dict_iterator)(struct workspace *wk, void *ctx, obj key, obj val); bool obj_dict_foreach(struct workspace *wk, obj dict, void *ctx, obj_dict_iterator cb); bool obj_dict_in(struct workspace *wk, obj dict, obj key); bool obj_dict_index(struct workspace *wk, obj dict, obj key, obj *res); bool obj_dict_index_strn(struct workspace *wk, obj dict, const char *str, uint32_t len, obj *res); void obj_dict_set(struct workspace *wk, obj dict, obj key, obj val); void obj_dict_dup(struct workspace *wk, obj dict, obj *res); void obj_dict_merge(struct workspace *wk, obj dict, obj dict2, obj *res); void obj_dict_merge_nodup(struct workspace *wk, obj dict, obj dict2); void obj_dict_seti(struct workspace *wk, obj dict, uint32_t key, obj val); bool obj_dict_geti(struct workspace *wk, obj dict, uint32_t key, obj *val); bool obj_iterable_foreach(struct workspace *wk, obj dict_or_array, void *ctx, obj_dict_iterator cb); #endif muon-0.2.0/include/lang/parser.h000066400000000000000000000037111441402162300164770ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_PARSER_H #define MUON_LANG_PARSER_H #include #include #include "data/darr.h" #include "lang/lexer.h" enum comparison_type { comp_equal, comp_nequal, comp_lt, comp_le, comp_gt, comp_ge, comp_in, comp_not_in // keep at the end, }; enum arithmetic_type { arith_add = 0, arith_sub = 1, arith_mod = 2, arith_mul = 3, arith_div = 4, }; enum arg_type { arg_normal, arg_kwarg, }; enum if_type { if_if, if_elseif, if_else, }; enum node_type { node_null, // only used for parsing node_bool, node_id, node_number, node_string, node_continue, node_break, node_argument, node_array, node_dict, node_empty, node_or, node_and, node_comparison, node_arithmetic, node_not, node_index, node_method, node_function, node_assignment, node_foreach, node_foreach_args, node_if, node_u_minus, node_ternary, node_block, node_stringify, /* formatting-only nodes */ node_empty_line, node_paren, node_plusassign, }; enum node_child_flag { node_child_l = 1 << 0, node_child_r = 1 << 1, node_child_c = 1 << 2, node_child_d = 1 << 3, }; enum { node_visited = 1 << 4, // for analyzer }; struct node { enum node_type type; uint32_t line, col; uint32_t subtype; union token_data dat; uint32_t l, r, c, d; struct { uint32_t start, len; } comments; uint8_t chflg; }; struct ast { struct darr nodes, comments; uint32_t root; }; enum parse_mode { pm_ignore_statement_with_no_effect = 1 << 0, pm_keep_formatting = 1 << 1, pm_quiet = 1 << 2, }; bool parser_parse(struct workspace *wk, struct ast *ast, struct source_data *sdata, struct source *src, enum parse_mode mode); void print_ast(struct ast *ast); struct node *get_node(struct ast *ast, uint32_t i); const char *node_to_s(struct node *n); const char *node_type_to_s(enum node_type t); void ast_destroy(struct ast *ast); #endif // MUON_PARSER_H muon-0.2.0/include/lang/serial.h000066400000000000000000000006251441402162300164630ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_SERIAL_H #define MUON_LANG_SERIAL_H #include "lang/workspace.h" bool serial_dump(struct workspace *wk_src, obj o, FILE *f); bool serial_load(struct workspace *wk, obj *res, FILE *f); bool serial_load_from_private_dir(struct workspace *wk, obj *res, const char *file); #endif muon-0.2.0/include/lang/string.h000066400000000000000000000055301441402162300165120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_STRING_H #define MUON_LANG_STRING_H #include "compat.h" #include "lang/object.h" struct workspace; #define WKSTR(cstring) (struct str){ .s = cstring, .len = strlen(cstring) } /* sbuf */ enum sbuf_flags { sbuf_flag_overflown = 1 << 0, sbuf_flag_overflow_obj_str = 0 << 1, // the default sbuf_flag_overflow_alloc = 1 << 1, sbuf_flag_overflow_error = 1 << 2, sbuf_flag_write = 1 << 3, sbuf_flag_string_exposed = 1 << 4, }; #define SBUF_CUSTOM(name, static_len, flags) \ struct sbuf name; \ char sbuf_static_buf_ ## name[static_len]; \ sbuf_init(&name, sbuf_static_buf_ ## name, static_len, flags); #define SBUF(name) SBUF_CUSTOM(name, 1024, 0) #define SBUF_manual(name) SBUF_CUSTOM(name, 1024, sbuf_flag_overflow_alloc) struct sbuf { char *buf; uint32_t len, cap; enum sbuf_flags flags; obj s; }; void sbuf_init(struct sbuf *sb, char *initial_buffer, uint32_t initial_buffer_cap, enum sbuf_flags flags); void sbuf_destroy(struct sbuf *sb); void sbuf_clear(struct sbuf *sb); void sbuf_grow(struct workspace *wk, struct sbuf *sb, uint32_t inc); void sbuf_push(struct workspace *wk, struct sbuf *sb, char s); void sbuf_pushn(struct workspace *wk, struct sbuf *sb, const char *s, uint32_t n); void sbuf_pushs(struct workspace *wk, struct sbuf *sb, const char *s); void sbuf_pushf(struct workspace *wk, struct sbuf *sb, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); obj sbuf_into_str(struct workspace *wk, struct sbuf *sb); void str_unescape(struct workspace *wk, struct sbuf *sb, const struct str *ss, bool escape_whitespace); bool str_has_null(const struct str *ss); const char *get_cstr(struct workspace *wk, obj s); obj make_str(struct workspace *wk, const char *str); obj make_strn(struct workspace *wk, const char *str, uint32_t n); obj make_strf(struct workspace *wk, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 2, 3); void str_app(struct workspace *wk, obj s, const char *str); void str_appf(struct workspace *wk, obj s, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); void str_appn(struct workspace *wk, obj s, const char *str, uint32_t n); obj str_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val); bool str_eql(const struct str *ss1, const struct str *ss2); bool str_startswith(const struct str *ss, const struct str *pre); bool str_endswith(const struct str *ss, const struct str *suf); obj str_join(struct workspace *wk, obj s1, obj s2); bool str_to_i(const struct str *ss, int64_t *res); obj str_split(struct workspace *wk, const struct str *ss, const struct str *split); obj str_strip(struct workspace *wk, const struct str *ss, const struct str *strip); obj str_split_strip(struct workspace *wk, const struct str *ss, const struct str *split, const struct str *strip); #endif muon-0.2.0/include/lang/types.h000066400000000000000000000003511441402162300163440ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_TYPES_H #define MUON_LANG_TYPES_H #include #include typedef uint32_t obj; #endif muon-0.2.0/include/lang/workspace.h000066400000000000000000000072001441402162300171760ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LANG_WORKSPACE_H #define MUON_LANG_WORKSPACE_H #include "buf_size.h" #include "data/bucket_array.h" #include "data/darr.h" #include "data/hash.h" #include "lang/eval.h" #include "lang/object.h" #include "lang/parser.h" #include "lang/string.h" struct project { struct hash scope; obj source_root, build_root, cwd, build_dir, subproject_name; obj opts, compilers, targets, tests, test_setups, summary; obj args, link_args, include_dirs; struct { obj static_deps, shared_deps; } dep_cache; obj wrap_provides_deps, wrap_provides_exes; // string obj rule_prefix; obj subprojects_dir; struct { obj name; obj version; obj license; bool no_version; } cfg; bool not_ok; // set by failed subprojects }; enum loop_ctl { loop_norm, loop_breaking, loop_continuing, }; enum { disabler_id = 1 }; struct workspace { const char *argv0, *source_root, *build_root, *muon_private; struct { uint32_t argc; char *const *argv; } original_commandline; /* Global objects * These should probably be cleaned up into a separate struct. * ----------------- */ /* obj_array that tracks files for build regeneration */ obj regenerate_deps; /* TODO host machine dict */ obj host_machine; /* TODO binaries dict */ obj binaries; obj install; obj install_scripts; obj postconf_scripts; obj subprojects; /* args dict for add_global_arguments() */ obj global_args; /* args dict for add_global_link_arguments() */ obj global_link_args; /* overridden dependencies dict */ obj dep_overrides_static, dep_overrides_dynamic; /* overridden find_program dict */ obj find_program_overrides; /* global options */ obj global_opts; /* dict[sha_512 -> [bool, any]] */ obj compiler_check_cache; /* ----------------- */ struct bucket_array chrs; struct bucket_array objs; struct bucket_array obj_aos[obj_type_count - _obj_aos_start]; struct darr projects; struct darr option_overrides; struct darr source_data; struct hash scope; struct hash obj_hash; uint32_t loop_depth, impure_loop_depth; enum loop_ctl loop_ctl; bool subdir_done; uint32_t cur_project; /* ast of current file */ struct ast *ast; /* source of current file */ struct source *src; /* interpreter base functions */ bool ((*interp_node)(struct workspace *wk, uint32_t node, obj *res)); void ((*assign_variable)(struct workspace *wk, const char *name, obj o, uint32_t n_id)); void ((*unassign_variable)(struct workspace *wk, const char *name)); bool ((*get_variable)(struct workspace *wk, const char *name, obj *res, uint32_t proj_id)); bool ((*eval_project_file)(struct workspace *wk, const char *path, bool first)); bool in_analyzer; enum language_mode lang_mode; struct { uint32_t node, last_line; bool stepping, break_on_err; obj watched; } dbg; #ifdef TRACY_ENABLE struct { bool is_master_workspace; } tracy; #endif }; bool get_obj_id(struct workspace *wk, const char *name, obj *res, uint32_t proj_id); void workspace_init_bare(struct workspace *wk); void workspace_init(struct workspace *wk); void workspace_destroy_bare(struct workspace *wk); void workspace_destroy(struct workspace *wk); bool workspace_setup_paths(struct workspace *wk, const char *build, const char *argv0, uint32_t argc, char *const argv[]); void workspace_add_regenerate_deps(struct workspace *wk, obj obj_or_arr); struct project *make_project(struct workspace *wk, uint32_t *id, const char *subproject_name, const char *cwd, const char *build_dir); struct project *current_project(struct workspace *wk); void workspace_print_summaries(struct workspace *wk, FILE *out); #endif muon-0.2.0/include/log.h000066400000000000000000000026111441402162300150410ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_LOG_H #define MUON_LOG_H #include "compat.h" #include #include #include #include #include enum log_level { log_quiet, log_error, log_warn, log_info, log_debug, log_level_count, }; extern const char *log_level_clr[log_level_count]; extern const char *log_level_name[log_level_count]; #define L(...) log_print(true, log_debug, __VA_ARGS__) #define LOG_I(...) log_print(true, log_info, __VA_ARGS__) #define LOG_W(...) log_print(true, log_warn, __VA_ARGS__) #define LOG_E(...) log_print(true, log_error, __VA_ARGS__) #define LL(...) log_print(false, log_debug, __VA_ARGS__) #define LLOG_I(...) log_print(false, log_info, __VA_ARGS__) #define LLOG_W(...) log_print(false, log_warn, __VA_ARGS__) #define LLOG_E(...) log_print(false, log_error, __VA_ARGS__) void log_init(void); void log_set_file(FILE *log_file); void log_set_lvl(enum log_level lvl); void log_set_prefix(const char *prefix); const char *log_get_prefix(void); void log_print(bool nl, enum log_level lvl, const char *fmt, ...) MUON_ATTR_FORMAT(printf, 3, 4); bool log_clr(void); void log_plain(const char *fmt, ...) MUON_ATTR_FORMAT(printf, 1, 2); void log_plainv(const char *fmt, va_list ap); FILE *log_file(void); bool log_should_print(enum log_level lvl); #endif muon-0.2.0/include/machine_file.h000066400000000000000000000004211441402162300166600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_MACHINE_FILE_H #define MUON_MACHINE_FILE_H #include "lang/workspace.h" bool machine_file_parse(struct workspace *dest_wk, const char *path); #endif muon-0.2.0/include/meson_opts.h000066400000000000000000000005411441402162300164460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_MESON_OPTS_H #define MUON_MESON_OPTS_H #include "lang/workspace.h" bool translate_meson_opts(struct workspace *wk, uint32_t argc, uint32_t argi, char *argv[], uint32_t *new_argc, uint32_t *new_argi, char **new_argv[]); #endif muon-0.2.0/include/options.h000066400000000000000000000035771441402162300157670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_OPTIONS_H #define MUON_OPTIONS_H #include "lang/workspace.h" extern bool initializing_builtin_options; struct option_override { // strings obj proj, name, val; enum option_value_source source; bool obj_value; }; bool create_option(struct workspace *wk, uint32_t node, obj opts, obj opt, obj val); bool get_option(struct workspace *wk, const struct project *proj, const struct str *name, obj *res); bool get_option_overridable(struct workspace *wk, const struct project *proj, obj overrides, const struct str *name, obj *res); void get_option_value(struct workspace *wk, const struct project *proj, const char *name, obj *res); void get_option_value_overridable(struct workspace *wk, const struct project *proj, obj overrides, const char *name, obj *res); bool check_invalid_option_overrides(struct workspace *wk); bool check_invalid_subproject_option(struct workspace *wk); bool prefix_dir_opts(struct workspace *wk); bool setup_project_options(struct workspace *wk, const char *cwd); bool init_global_options(struct workspace *wk); bool parse_and_set_cmdline_option(struct workspace *wk, char *lhs); bool parse_and_set_default_options(struct workspace *wk, uint32_t err_node, obj arr, obj project_name, bool for_subproject); bool parse_and_set_override_options(struct workspace *wk, uint32_t err_node, obj arr, obj *res); enum wrap_mode { wrap_mode_nopromote, wrap_mode_nodownload, wrap_mode_nofallback, wrap_mode_forcefallback, }; enum wrap_mode get_option_wrap_mode(struct workspace *wk); enum tgt_type get_option_default_library(struct workspace *wk); bool get_option_bool(struct workspace *wk, obj overrides, const char *name, bool fallback); struct list_options_opts { bool list_all, only_modified; }; bool list_options(const struct list_options_opts *list_opts); #endif muon-0.2.0/include/opts.h000066400000000000000000000032021441402162300152420ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_OPTS_H #define MUON_OPTS_H #include #include "lang/workspace.h" /* OPTSTART should be pretty self-explanatory. You just pass it the optstring * that you would pass to getopt(). "h" is added to this optstring for you. * * OPTEND is a little bit more involved, the first 4 arguments are used to * construct the help message, while the 5th argument should be the number of * required operands for this subcommand, or -1 which disables the check. */ #define OPTSTART(optstring) \ signed char opt; \ optind = 1; \ while ((opt = getopt(argc - argi, &argv[argi], optstring "h")) != -1) { \ switch (opt) { #define OPTEND(usage_pre, usage_post, usage_opts, commands, operands) \ case 'h': \ print_usage(stdout, commands, usage_pre, usage_opts, usage_post); \ exit(0); \ break; \ default: \ print_usage(stderr, commands, usage_pre, usage_opts, usage_post); \ return false; \ } \ } \ if (!check_operands(argc, (argi + optind), operands)) { \ print_usage(stderr, commands, usage_pre, usage_opts, usage_post); \ return false; \ } \ argi += optind; typedef bool (*cmd_func)(uint32_t argc, uint32_t argi, char *const[]); struct command { const char *name; cmd_func cmd; const char *desc; }; void print_usage(FILE *f, const struct command *commands, const char *pre, const char *opts, const char *post); bool find_cmd(const struct command *commands, cmd_func *ret, uint32_t argc, uint32_t argi, char *const argv[], bool optional); bool check_operands(uint32_t argc, uint32_t argi, int32_t expected); #endif muon-0.2.0/include/platform/000077500000000000000000000000001441402162300157335ustar00rootroot00000000000000muon-0.2.0/include/platform/filesystem.h000066400000000000000000000043411441402162300202720ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_FILESYSTEM_H #define MUON_PLATFORM_FILESYSTEM_H #include #include #include #include #include "iterator.h" struct source { const char *label; const char *src; uint64_t len; }; struct workspace; struct sbuf; bool fs_stat(const char *path, struct stat *sb); bool fs_exists(const char *path); bool fs_file_exists(const char *path); bool fs_symlink_exists(const char *path); bool fs_exe_exists(const char *path); bool fs_dir_exists(const char *path); bool fs_mkdir(const char *path); bool fs_mkdir_p(const char *path); bool fs_read_entire_file(const char *path, struct source *src); bool fs_fsize(FILE *file, uint64_t *ret); bool fs_fclose(FILE *file); FILE *fs_fopen(const char *path, const char *mode); bool fs_fwrite(const void *ptr, size_t size, FILE *f); bool fs_fread(void *ptr, size_t size, FILE *f); bool fs_write(const char *path, const uint8_t *buf, uint64_t buf_len); bool fs_find_cmd(struct workspace *wk, struct sbuf *buf, const char *cmd); bool fs_has_cmd(const char *cmd); void fs_source_destroy(struct source *src); void fs_source_dup(const struct source *src, struct source *dup); bool fs_redirect(const char *path, const char *mode, int fd, int *old_fd); bool fs_redirect_restore(int fd, int old_fd); bool fs_copy_file(const char *src, const char *dest); bool fs_copy_dir(const char *src_base, const char *dest_base); bool fs_fileno(FILE *f, int *ret); bool fs_make_symlink(const char *target, const char *path, bool force); bool fs_fseek(FILE *file, size_t off); bool fs_ftell(FILE *file, uint64_t *res); const char *fs_user_home(void); bool fs_is_a_tty_from_fd(int fd); bool fs_is_a_tty(FILE *f); bool fs_chmod(const char *path, uint32_t mode); bool fs_copy_metadata(const char *src, const char *dest); /* Windows only */ bool fs_has_extension(const char *path, const char *ext); typedef enum iteration_result ((*fs_dir_foreach_cb)(void *_ctx, const char *path)); bool fs_dir_foreach(const char *path, void *_ctx, fs_dir_foreach_cb cb); #ifndef S_ISGID #define S_ISGID 0 #endif #ifndef S_ISUID #define S_ISUID 0 #endif #ifndef S_ISVTX #define S_ISVTX 0 #endif #endif muon-0.2.0/include/platform/log.h000066400000000000000000000003611441402162300166650ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_LOG_H #define MUON_PLATFORM_LOG_H #include void print_colorized(FILE *out, const char *s); #endif muon-0.2.0/include/platform/mem.h000066400000000000000000000005131441402162300166610ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_MEM_H #define MUON_PLATFORM_MEM_H #include void *z_calloc(size_t nmemb, size_t size); void *z_malloc(size_t size); void *z_realloc(void *ptr, size_t size); void z_free(void *ptr); #endif muon-0.2.0/include/platform/path.h000066400000000000000000000033751441402162300170500ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_PATH_H #define MUON_PLATFORM_PATH_H #include #include #define PATH_SEP '/' #ifdef _WIN32 #define ENV_PATH_SEP ';' #else #define ENV_PATH_SEP ':' #endif #ifdef _WIN32 #define ENV_PATH_SEP_STR ";" #else #define ENV_PATH_SEP_STR ":" #endif struct workspace; struct sbuf; void path_init(void); void path_deinit(void); bool path_chdir(const char *path); void path_copy(struct workspace *wk, struct sbuf *sb, const char *path); void path_cwd(struct workspace *wk, struct sbuf *sb); bool path_is_absolute(const char *path); bool path_is_basename(const char *path); bool path_is_subpath(const char *base, const char *sub); void path_push(struct workspace *wk, struct sbuf *sb, const char *b); void path_join(struct workspace *wk, struct sbuf *sb, const char *a, const char *b); // like path_join but won't discard a if b is an absolute path void path_join_absolute(struct workspace *wk, struct sbuf *sb, const char *a, const char *b); void path_make_absolute(struct workspace *wk, struct sbuf *buf, const char *path); void path_relative_to(struct workspace *wk, struct sbuf *buf, const char *base_raw, const char *path_raw); void path_without_ext(struct workspace *wk, struct sbuf *buf, const char *path); void path_basename(struct workspace *wk, struct sbuf *buf, const char *path); void path_dirname(struct workspace *wk, struct sbuf *buf, const char *path); void path_executable(struct workspace *wk, struct sbuf *buf, const char *path); void _path_normalize(struct workspace *wk, struct sbuf *buf, bool optimize); void path_to_posix(char *path); void shell_escape(struct workspace *wk, struct sbuf *sb, const char *str); #endif muon-0.2.0/include/platform/rpath_fixer.h000066400000000000000000000004461441402162300204230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_RPATH_FIXER_H #define MUON_PLATFORM_RPATH_FIXER_H #include #include bool fix_rpaths(const char *elf_path, const char *build_root); #endif muon-0.2.0/include/platform/run_cmd.h000066400000000000000000000035421441402162300175370ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_RUN_CMD_H #define MUON_PLATFORM_RUN_CMD_H #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #endif enum run_cmd_state { run_cmd_error, run_cmd_running, run_cmd_finished, }; struct run_cmd_pipe_ctx { size_t size; size_t len; char *buf; }; enum run_cmd_ctx_flags { run_cmd_ctx_flag_async = 1 << 0, run_cmd_ctx_flag_dont_capture = 1 << 1, }; struct run_cmd_ctx { struct run_cmd_pipe_ctx err, out; const char *err_msg; // set on error const char *chdir; // set by caller const char *stdin_path; // set by caller int status; enum run_cmd_ctx_flags flags; #ifdef _WIN32 HANDLE process; bool close_pipes; struct { OVERLAPPED overlap; HANDLE pipe[2]; } pipe_out, pipe_err; #else int pipefd_out[2], pipefd_err[2]; int input_fd; pid_t pid; bool input_fd_open; bool pipefd_out_open[2], pipefd_err_open[2]; #endif }; void push_argv_single(const char **argv, uint32_t *len, uint32_t max, const char *arg); void argstr_pushall(const char *argstr, uint32_t argc, const char **argv, uint32_t *argi, uint32_t max); /* * argstr is a NUL delimited array of strings * envstr is like argstr, every two strings is considered a key/value pair */ uint32_t argstr_to_argv(const char *argstr, uint32_t argc, const char *prepend, char *const **res); bool run_cmd(struct run_cmd_ctx *ctx, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc); bool run_cmd_argv(struct run_cmd_ctx *ctx, char *const *argv, const char *envstr, uint32_t envc); enum run_cmd_state run_cmd_collect(struct run_cmd_ctx *ctx); void run_cmd_ctx_destroy(struct run_cmd_ctx *ctx); bool run_cmd_kill(struct run_cmd_ctx *ctx, bool force); #endif muon-0.2.0/include/platform/term.h000066400000000000000000000004271441402162300170560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_TERM_H #define MUON_PLATFORM_TERM_H #include #include bool term_winsize(int fd, uint32_t *height, uint32_t *width); #endif muon-0.2.0/include/platform/uname.h000066400000000000000000000005601441402162300172120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_UNAME_H #define MUON_PLATFORM_UNAME_H #include enum endianness { big_endian, little_endian, }; bool uname_sysname(const char **res); bool uname_machine(const char **res); bool uname_endian(enum endianness *res); #endif muon-0.2.0/include/platform/windows/000077500000000000000000000000001441402162300174255ustar00rootroot00000000000000muon-0.2.0/include/platform/windows/log.h000066400000000000000000000004241441402162300203570ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_LOG_H #define MUON_PLATFORM_WINDOWS_LOG_H extern bool tty_is_pty; #endif muon-0.2.0/include/platform/windows/win32_error.h000066400000000000000000000004531441402162300217530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_WIN32_ERROR_H #define MUON_PLATFORM_WINDOWS_WIN32_ERROR_H const char *win32_error(void); #endif muon-0.2.0/include/platform/windows/win32_getopt.h000066400000000000000000000005501441402162300221220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Vincent Torri * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_PLATFORM_WINDOWS_GETOPT_H #define MUON_PLATFORM_WINDOWS_GETOPT_H int getopt(int, char * const [], const char *); extern char *optarg; extern int optind, opterr, optopt; #endif muon-0.2.0/include/rpmvercmp.h000066400000000000000000000003751441402162300163000ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_RPMVERCMP_H #define MUON_RPMVERCMP_H #include "lang/string.h" int8_t rpmvercmp(const struct str *a, const struct str *b); #endif muon-0.2.0/include/sha_256.h000066400000000000000000000004211441402162300154240ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_SHA_256_H #define MUON_SHA_256_H #include #include void calc_sha_256(uint8_t hash[32], const void *input, size_t len); #endif muon-0.2.0/include/tracy.h000066400000000000000000000021701441402162300154020ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifdef TRACY_ENABLE #include "TracyC.h" #define TracyCZoneAutoS TracyCZoneN(tctx_func, __func__, true) #define TracyCZoneAutoE TracyCZoneEnd(tctx_func) #else #define TracyCZoneAutoS #define TracyCZoneAutoE #define TracyCZone(c, x) #define TracyCZoneN(c, x, y) #define TracyCZoneC(c, x, y) #define TracyCZoneNC(c, x, y, z) #define TracyCZoneEnd(c) #define TracyCZoneText(c, x, y) #define TracyCZoneName(c, x, y) #define TracyCZoneValue(c, x) #define TracyCAlloc(x, y) #define TracyCFree(x) #define TracyCSecureAlloc(x, y) #define TracyCSecureFree(x) #define TracyCFrameMark #define TracyCFrameMarkNamed(x) #define TracyCFrameMarkStart(x) #define TracyCFrameMarkEnd(x) #define TracyCFrameImage(x, y, z, w, a) #define TracyCPlot(x, y) #define TracyCMessage(x, y) #define TracyCMessageL(log_misc, x) #define TracyCMessageC(x, y, z) #define TracyCMessageLC(x, y) #define TracyCAppInfo(x, y) #define TracyCZoneS(x, y, z) #define TracyCZoneNS(x, y, z, w) #define TracyCZoneCS(x, y, z, w) #define TracyCZoneNCS(x, y, z, w, a) #endif muon-0.2.0/include/version.h000066400000000000000000000005571441402162300157540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_VERSION_H #define MUON_VERSION_H struct muon_version { const char *const version, *const vcs_tag, *const meson_compat; }; extern const struct muon_version muon_version; #endif muon-0.2.0/include/wrap.h000066400000000000000000000027341441402162300152370ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MUON_WRAP_H #define MUON_WRAP_H #include "compat.h" #include #include #include "buf_size.h" #include "lang/string.h" #include "lang/types.h" #include "platform/filesystem.h" struct workspace; enum wrap_fields { // wrap wf_directory, wf_patch_url, wf_patch_fallback_url, wf_patch_filename, wf_patch_hash, wf_patch_directory, wf_diff_files, // wrap-file wf_source_url, wf_source_fallback_url, wf_source_filename, wf_source_hash, wf_lead_directory_missing, // wrap-git wf_url, wf_revision, wf_depth, wf_push_url, wf_clone_recursive, wf_wrapdb_version, // ?? undocumented wrap_fields_count, }; enum wrap_type { wrap_type_file, wrap_type_git, wrap_provide, wrap_type_count, }; struct wrap { struct source src; enum wrap_type type; bool has_provides; const char *fields[wrap_fields_count]; char *buf; char dest_dir_buf[BUF_SIZE_1k], name_buf[BUF_SIZE_1k]; struct sbuf dest_dir, name; }; enum wrap_provides_key { wrap_provides_key_override_dependencies, wrap_provides_key_override_executables, wrap_provides_key_dependency_variables, }; void wrap_destroy(struct wrap *wrap); bool wrap_parse(const char *wrap_file, struct wrap *wrap); bool wrap_handle(const char *wrap_file, const char *subprojects, struct wrap *wrap, bool download); bool wrap_load_all_provides(struct workspace *wk, const char *subprojects); #endif muon-0.2.0/meson.build000066400000000000000000000051341441402162300146310ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-FileCopyrightText: Simon Zeni # SPDX-License-Identifier: GPL-3.0-only project( 'muon', 'c', version: '0.2.0', license: 'GPL-3.0-only', meson_version: '>=0.59.0', default_options: [ 'c_std=c99', 'warning_level=3', 'buildtype=debugoptimized', 'default_library=static', ], ) fs = import('fs') # version information git = find_program('git', required: false) if git.found() and fs.is_dir('.git') git_rev_parse = run_command(git, 'rev-parse', '--short', '@', check: true) git_sha = git_rev_parse.stdout().strip() else git_sha = '' endif version_info = configuration_data() version_info.set('version', meson.project_version()) version_info.set('vcs_tag', git_sha) version_info.set('meson_compat', '0.64') # platform platform = host_machine.system() if ( platform in [ 'cygwin', 'darwin', 'freebsd', 'linux', 'msys2', 'netbsd', 'openbsd', 'sunos', 'emscripten', ] ) platform = 'posix' elif platform not in ['windows'] warning( 'configuring muon for an unsupported platform "@0@"'.format(platform), ) platform = 'null' endif # compiler setup c_args = ['-DMUON_PLATFORM_' + platform] link_args = [] if get_option('static') c_args += '-DMUON_STATIC' link_args += '-static' endif cc = meson.get_compiler('c') add_project_arguments( cc.get_supported_arguments( [ '-Wno-missing-braces', '-Wendif-labels', '-Wimplicit-fallthrough=2', '-Winit-self', '-Wlogical-op', '-Wmissing-include-dirs', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wold-style-definition', '-Woverflow', '-Wstrict-aliasing=2', '-Wstrict-prototypes', '-Wundef', '-fstrict-aliasing', ], ), language: 'c', ) add_project_arguments('-DMUON_BOOTSTRAPPED', language: 'c') include_dir = [include_directories('include')] subdir('tools') subdir('src') # tracy tracy_dep = dependency('tracy', required: get_option('tracy')) if tracy_dep.found() add_languages('cpp') c_args += ['-DTRACY_ENABLE'] deps += tracy_dep endif muon = executable( 'muon', src, dependencies: deps, include_directories: include_dir, link_args: link_args, c_args: c_args, cpp_args: c_args, install: true, ) python3 = find_program('python3', required: false) subdir('tests') subdir('doc') muon-0.2.0/meson_options.txt000066400000000000000000000022411441402162300161200ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only option( 'libcurl', type: 'feature', value: 'auto', description: 'required for fetching wraps', ) option( 'libarchive', type: 'feature', value: 'auto', description: 'required for extracting wrap archives', ) option( 'libpkgconf', type: 'feature', value: 'auto', description: 'required for dependency discovery with pkg-config files', ) option( 'samurai', type: 'feature', value: 'disabled', description: 'embed samurai into the muon executable', ) option( 'bestline', type: 'feature', value: 'enabled', description: 'add readline support via bestline', ) option( 'static', type: 'boolean', value: false, description: 'build a static muon executable', ) option( 'docs', type: 'feature', value: 'auto', description: 'build documentation', ) option( 'website', type: 'boolean', value: false, description: 'build website, requires docs to be enabled', ) option( 'tracy', type: 'feature', value: 'auto', description: 'whether to enable tracy', ) muon-0.2.0/src/000077500000000000000000000000001441402162300132535ustar00rootroot00000000000000muon-0.2.0/src/amalgam.c000066400000000000000000000070461441402162300150250ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #ifdef __sun #define __EXTENSIONS__ #endif #include "args.c" #include "backend/backend.c" #include "backend/common_args.c" #include "backend/ninja.c" #include "backend/ninja/alias_target.c" #include "backend/ninja/build_target.c" #include "backend/ninja/custom_target.c" #include "backend/ninja/rules.c" #include "backend/output.c" #include "cmd_install.c" #include "cmd_test.c" #include "coerce.c" #include "compilers.c" #include "data/bucket_array.c" #include "data/darr.c" #include "data/hash.c" #include "embedded.c" #include "error.c" #include "external/bestline_null.c" #include "external/libarchive_null.c" #include "external/libcurl_null.c" #include "external/samurai_null.c" #include "formats/ini.c" #include "formats/lines.c" #include "formats/tap.c" #include "functions/array.c" #include "functions/boolean.c" #include "functions/both_libs.c" #include "functions/build_target.c" #include "functions/common.c" #include "functions/compiler.c" #include "functions/configuration_data.c" #include "functions/custom_target.c" #include "functions/dependency.c" #include "functions/dict.c" #include "functions/disabler.c" #include "functions/environment.c" #include "functions/external_program.c" #include "functions/feature_opt.c" #include "functions/file.c" #include "functions/generator.c" #include "functions/kernel.c" #include "functions/kernel/build_target.c" #include "functions/kernel/configure_file.c" #include "functions/kernel/custom_target.c" #include "functions/kernel/dependency.c" #include "functions/kernel/install.c" #include "functions/kernel/options.c" #include "functions/kernel/subproject.c" #include "functions/machine.c" #include "functions/meson.c" #include "functions/modules.c" #include "functions/modules/fs.c" #include "functions/modules/keyval.c" #include "functions/modules/pkgconfig.c" #include "functions/modules/python.c" #include "functions/modules/sourceset.c" #include "functions/number.c" #include "functions/run_result.c" #include "functions/source_configuration.c" #include "functions/source_set.c" #include "functions/string.c" #include "functions/subproject.c" #include "guess.c" #include "install.c" #include "lang/analyze.c" #include "lang/eval.c" #include "lang/fmt.c" #include "lang/interpreter.c" #include "lang/lexer.c" #include "lang/object.c" #include "lang/parser.c" #include "lang/serial.c" #include "lang/string.c" #include "lang/workspace.c" #include "log.c" #include "machine_file.c" #include "main.c" #include "meson_opts.c" #include "options.c" #include "opts.c" #include "platform/filesystem.c" #include "platform/mem.c" #include "platform/path.c" #include "platform/run_cmd.c" #include "platform/uname.c" #include "rpmvercmp.c" #include "sha_256.c" #include "version.c.in" #include "wrap.c" #ifdef _WIN32 #include "platform/windows/filesystem.c" #include "platform/windows/log.c" #include "platform/windows/path.c" #include "platform/windows/rpath_fixer.c" #include "platform/windows/run_cmd.c" #include "platform/windows/term.c" #include "platform/windows/uname.c" #include "platform/windows/win32_error.c" #include "platform/windows/win32_getopt.c" #else #include "platform/null/rpath_fixer.c" #include "platform/posix/filesystem.c" #include "platform/posix/log.c" #include "platform/posix/path.c" #include "platform/posix/run_cmd.c" #include "platform/posix/term.c" #include "platform/posix/uname.c" #endif #ifdef BOOTSTRAP_HAVE_LIBPKGCONF #include "external/libpkgconf.c" #else #include "external/libpkgconf_null.c" #endif muon-0.2.0/src/args.c000066400000000000000000000150371441402162300143610ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: dffdff2423 * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "compilers.h" #include "functions/build_target.h" #include "functions/environment.h" #include "lang/interpreter.h" #include "lang/workspace.h" #include "log.h" #include "platform/path.h" #include "error.h" void push_args(struct workspace *wk, obj arr, const struct args *args) { uint32_t i; for (i = 0; i < args->len; ++i) { obj_array_push(wk, arr, make_str(wk, args->args[i])); } } void push_args_null_terminated(struct workspace *wk, obj arr, char *const *argv) { char *const *arg; for (arg = argv; *arg; ++arg) { obj_array_push(wk, arr, make_str(wk, *arg)); } } static void simple_escape(struct workspace *wk, struct sbuf *sb, const char *str, const char *need_escaping, char esc_char) { const char *s = str; for (; *s; ++s) { if (strchr(need_escaping, *s)) { sbuf_push(wk, sb, esc_char); } else if (*s == '\n') { assert(false && "newlines cannot be escaped"); } sbuf_push(wk, sb, *s); } } void ninja_escape(struct workspace *wk, struct sbuf *sb, const char *str) { simple_escape(wk, sb, str, " :$", '$'); } static void shell_ninja_escape(struct workspace *wk, struct sbuf *sb, const char *str) { SBUF_manual(tmp); shell_escape(wk, &tmp, str); simple_escape(wk, sb, tmp.buf, "$\n", '$'); sbuf_destroy(&tmp); } void pkgconf_escape(struct workspace *wk, struct sbuf *sb, const char *str) { simple_escape(wk, sb, str, " ", '\\'); } typedef void ((*escape_func)(struct workspace *wk, struct sbuf *sb, const char *str)); struct join_args_iter_ctx { uint32_t i, len; uint32_t *obj; escape_func escape; }; static enum iteration_result join_args_iter(struct workspace *wk, void *_ctx, obj val) { struct join_args_iter_ctx *ctx = _ctx; const char *s = get_cstr(wk, val); SBUF(esc); if (ctx->escape) { ctx->escape(wk, &esc, s); s = esc.buf; } str_app(wk, *ctx->obj, s); if (ctx->i < ctx->len - 1) { str_app(wk, *ctx->obj, " "); } ++ctx->i; return ir_cont; } static obj join_args(struct workspace *wk, obj arr, escape_func escape) { obj o = make_str(wk, ""); struct join_args_iter_ctx ctx = { .obj = &o, .len = get_obj_array(wk, arr)->len, .escape = escape }; obj_array_foreach(wk, arr, &ctx, join_args_iter); return o; } obj join_args_plain(struct workspace *wk, obj arr) { return join_args(wk, arr, NULL); } obj join_args_shell(struct workspace *wk, obj arr) { return join_args(wk, arr, shell_escape); } obj join_args_ninja(struct workspace *wk, obj arr) { return join_args(wk, arr, ninja_escape); } obj join_args_shell_ninja(struct workspace *wk, obj arr) { return join_args(wk, arr, shell_ninja_escape); } obj join_args_pkgconf(struct workspace *wk, obj arr) { return join_args(wk, arr, pkgconf_escape); } struct arr_to_args_ctx { enum arr_to_args_flags mode; obj res; }; static enum iteration_result arr_to_args_iter(struct workspace *wk, void *_ctx, obj src) { struct arr_to_args_ctx *ctx = _ctx; obj str; enum obj_type t = get_obj_type(wk, src); switch (t) { case obj_string: str = src; break; case obj_file: if (ctx->mode & arr_to_args_relativize_paths) { SBUF(rel); path_relative_to(wk, &rel, wk->build_root, get_file_path(wk, src)); str = sbuf_into_str(wk, &rel); break; } str = *get_obj_file(wk, src); break; case obj_alias_target: if (!(ctx->mode & arr_to_args_alias_target)) { goto type_err; } str = get_obj_alias_target(wk, src)->name; break; case obj_both_libs: src = get_obj_both_libs(wk, src)->dynamic_lib; /* fallthrough */ case obj_build_target: { if (!(ctx->mode & arr_to_args_build_target)) { goto type_err; } struct obj_build_target *tgt = get_obj_build_target(wk, src); SBUF(rel); if (ctx->mode & arr_to_args_relativize_paths) { path_relative_to(wk, &rel, wk->build_root, get_cstr(wk, tgt->build_path)); str = sbuf_into_str(wk, &rel); } else { str = tgt->build_path; } break; } case obj_custom_target: { if (!(ctx->mode & arr_to_args_custom_target)) { goto type_err; } struct obj_custom_target* custom_tgt = get_obj_custom_target(wk, src); obj output_arr = custom_tgt->output; // run_target is a custom_target without an output, so we yield // the target's name and continue. if (!get_obj_type(wk, output_arr)) { obj_array_push(wk, ctx->res, custom_tgt->name); return ir_cont; } if (!obj_array_foreach(wk, output_arr, ctx, arr_to_args_iter)) { return ir_err; } return ir_cont; } case obj_external_program: if (!(ctx->mode & arr_to_args_external_program)) { goto type_err; } str = get_obj_external_program(wk, src)->full_path; break; case obj_compiler: obj_array_extend(wk, ctx->res, get_obj_compiler(wk, src)->cmd_arr); return ir_cont; default: type_err: LOG_E("cannot convert '%s' to argument", obj_type_to_s(t)); return ir_err; } obj_array_push(wk, ctx->res, str); return ir_cont; } bool arr_to_args(struct workspace *wk, enum arr_to_args_flags mode, obj arr, obj *res) { make_obj(wk, res, obj_array); struct arr_to_args_ctx ctx = { .mode = mode, .res = *res }; return obj_array_foreach_flat(wk, arr, &ctx, arr_to_args_iter); } struct join_args_argstr_ctx { obj str; }; static enum iteration_result join_args_argstr_iter(struct workspace *wk, void *_ctx, obj v) { struct join_args_argstr_ctx *ctx = _ctx; const struct str *s = get_str(wk, v); str_appn(wk, ctx->str, s->s, s->len + 1); return ir_cont; } void join_args_argstr(struct workspace *wk, const char **res, uint32_t *argc, obj arr) { struct join_args_argstr_ctx ctx = { .str = make_str(wk, ""), }; obj_array_foreach(wk, arr, &ctx, join_args_argstr_iter); *res = get_str(wk, ctx.str)->s; *argc = get_obj_array(wk, arr)->len; } struct env_to_envstr_ctx { obj str; }; static enum iteration_result env_to_envstr_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct env_to_envstr_ctx *ctx = _ctx; const struct str *k = get_str(wk, key), *v = get_str(wk, val); str_appn(wk, ctx->str, k->s, k->len + 1); str_appn(wk, ctx->str, v->s, v->len + 1); return ir_cont; } void env_to_envstr(struct workspace *wk, const char **res, uint32_t *envc, obj val) { struct env_to_envstr_ctx ctx = { .str = make_str(wk, ""), }; obj dict; if (!environment_to_dict(wk, val, &dict)) { UNREACHABLE; } obj_dict_foreach(wk, dict, &ctx, env_to_envstr_dict_iter); *res = get_str(wk, ctx.str)->s; *envc = get_obj_dict(wk, dict)->len; } muon-0.2.0/src/backend/000077500000000000000000000000001441402162300146425ustar00rootroot00000000000000muon-0.2.0/src/backend/backend.c000066400000000000000000000027041441402162300164000ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "args.h" #include "backend/backend.h" #include "backend/ninja.h" #include "functions/environment.h" #include "log.h" #include "platform/run_cmd.h" #include "tracy.h" static enum iteration_result run_postconf_script_iter(struct workspace *wk, void *_ctx, obj arr) { TracyCZoneAutoS; enum iteration_result ret = ir_err; obj env; make_obj(wk, &env, obj_dict); set_default_environment_vars(wk, env, false); const char *argstr, *envstr; uint32_t argc, envc; env_to_envstr(wk, &envstr, &envc, env); join_args_argstr(wk, &argstr, &argc, arr); LOG_I("running postconf script '%s'", argstr); struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { LOG_E("failed to run postconf script: %s", cmd_ctx.err_msg); goto ret; } if (cmd_ctx.status != 0) { LOG_E("postconf script failed"); LOG_E("stdout: %s", cmd_ctx.out.buf); LOG_E("stderr: %s", cmd_ctx.err.buf); goto ret; } ret = ir_cont; ret: run_cmd_ctx_destroy(&cmd_ctx); TracyCZoneAutoE; return ret; } bool backend_output(struct workspace *wk) { TracyCZoneAutoS; if (!ninja_write_all(wk)) { LOG_E("backend output failed"); TracyCZoneAutoE; return false; } if (!obj_array_foreach(wk, wk->postconf_scripts, NULL, run_postconf_script_iter)) { TracyCZoneAutoE; return false; } TracyCZoneAutoE; return true; } muon-0.2.0/src/backend/common_args.c000066400000000000000000000421711441402162300173170ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Owen Rafferty * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "backend/common_args.h" #include "error.h" #include "functions/dependency.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" static void get_option_value_for_tgt(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, const char *name, obj *res) { get_option_value_overridable(wk, proj, tgt ? tgt->override_options : 0, name, res); } static bool get_buildtype_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_type t) { uint32_t i; enum compiler_optimization_lvl opt = 0; bool debug = false; static struct { const char *name; enum compiler_optimization_lvl opt; bool debug; } tbl[] = { { "plain", compiler_optimization_lvl_none, false }, { "debug", compiler_optimization_lvl_0, true }, { "debugoptimized", compiler_optimization_lvl_g, true }, { "release", compiler_optimization_lvl_3, false }, { "minsize", compiler_optimization_lvl_s, false }, { NULL } }; obj buildtype_opt_id, buildtype; get_option_overridable(wk, proj, tgt ? tgt->override_options : 0, &WKSTR("buildtype"), &buildtype_opt_id); struct obj_option *buildtype_opt = get_obj_option(wk, buildtype_opt_id); buildtype = buildtype_opt->val; const char *str = get_cstr(wk, buildtype); bool use_custom = (strcmp(str, "custom") == 0) || (buildtype_opt->source <= option_value_source_default); if (use_custom) { obj optimization_id, debug_id; get_option_value_for_tgt(wk, proj, tgt, "optimization", &optimization_id); get_option_value_for_tgt(wk, proj, tgt, "debug", &debug_id); const struct str *str = get_str(wk, optimization_id); if (str_eql(str, &WKSTR("plain"))) { opt = compiler_optimization_lvl_none; } else if (str->len != 1) { UNREACHABLE; } switch (*str->s) { case '0': case '1': case '2': case '3': opt = compiler_optimization_lvl_0 + (*str->s - '0'); break; case 'g': opt = compiler_optimization_lvl_g; break; case 's': opt = compiler_optimization_lvl_s; break; default: UNREACHABLE; } debug = get_obj_bool(wk, debug_id); } else { for (i = 0; tbl[i].name; ++i) { if (strcmp(str, tbl[i].name) == 0) { opt = tbl[i].opt; debug = tbl[i].debug; break; } } if (!tbl[i].name) { LOG_E("invalid build type %s", str); return false; } } if (debug) { push_args(wk, args_id, compilers[t].args.debug()); } push_args(wk, args_id, compilers[t].args.optimization(opt)); return true; } static void get_warning_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_type t) { obj lvl_id; get_option_value_for_tgt(wk, proj, tgt, "warning_level", &lvl_id); uint32_t lvl; const struct str *sl = get_str(wk, lvl_id); assert(sl->len == 1 && "invalid warning_level"); switch (sl->s[0]) { case '0': lvl = 0; break; case '1': lvl = 1; break; case '2': lvl = 2; break; case '3': lvl = 3; break; default: UNREACHABLE; return; } push_args(wk, args_id, compilers[t].args.warning_lvl(lvl)); } static void get_werror_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_type t) { obj active; get_option_value_for_tgt(wk, proj, tgt, "werror", &active); if (get_obj_bool(wk, active)) { push_args(wk, args_id, compilers[t].args.werror()); } } void get_std_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang, enum compiler_type t) { obj std; switch (lang) { case compiler_language_c: get_option_value_for_tgt(wk, proj, tgt, "c_std", &std); break; case compiler_language_cpp: get_option_value_for_tgt(wk, proj, tgt, "cpp_std", &std); break; default: return; } const char *s = get_cstr(wk, std); if (strcmp(s, "none") != 0) { push_args(wk, args_id, compilers[t].args.set_std(s)); } } void get_option_compile_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang) { obj args; switch (lang) { case compiler_language_c: get_option_value_for_tgt(wk, proj, tgt, "c_args", &args); break; case compiler_language_cpp: get_option_value_for_tgt(wk, proj, tgt, "cpp_args", &args); break; default: return; } obj_array_extend(wk, args_id, args); } struct setup_compiler_args_includes_ctx { obj args; enum compiler_type t; bool dont_relativize; }; static enum iteration_result setup_compiler_args_includes_iter(struct workspace *wk, void *_ctx, obj v) { struct setup_compiler_args_includes_ctx *ctx = _ctx; const char *dir; bool is_system; { enum obj_type t = get_obj_type(wk, v); switch (t) { case obj_include_directory: { struct obj_include_directory *inc = get_obj_include_directory(wk, v); dir = get_cstr(wk, inc->path); is_system = inc->is_system; break; } case obj_string: dir = get_cstr(wk, v); is_system = false; break; default: LOG_E("invalid type for include directory '%s'", obj_type_to_s(t)); UNREACHABLE; } } SBUF(rel); if (!ctx->dont_relativize) { if (!fs_dir_exists(dir)) { return ir_cont; } if (path_is_absolute(dir)) { path_relative_to(wk, &rel, wk->build_root, dir); dir = rel.buf; } } if (is_system) { push_args(wk, ctx->args, compilers[ctx->t].args.include_system(dir)); } else { push_args(wk, ctx->args, compilers[ctx->t].args.include(dir)); } return ir_cont; } struct setup_compiler_args_ctx { const struct obj_build_target *tgt; const struct project *proj; obj include_dirs; obj dep_args; obj joined_args; }; static void setup_optional_b_args_compiler(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args, enum compiler_type t) { #ifndef MUON_BOOTSTRAPPED // If we aren't bootstrapped, we don't yet have any b_ options defined return; #endif obj opt; get_option_value_for_tgt(wk, proj, tgt, "b_pgo", &opt); if (!str_eql(get_str(wk, opt), &WKSTR("off"))) { uint32_t stage; const struct str *sl = get_str(wk, opt); if (str_eql(sl, &WKSTR("generate"))) { stage = 0; } else if (str_eql(sl, &WKSTR("use"))) { stage = 1; } else { UNREACHABLE; return; } push_args(wk, args, compilers[t].args.pgo(stage)); } get_option_value_for_tgt(wk, proj, tgt, "b_sanitize", &opt); if (!str_eql(get_str(wk, opt), &WKSTR("none"))) { push_args(wk, args, compilers[t].args.sanitize(get_cstr(wk, opt))); } obj buildtype; get_option_value_for_tgt(wk, proj, tgt, "buildtype", &buildtype); get_option_value_for_tgt(wk, proj, tgt, "b_ndebug", &opt); if (str_eql(get_str(wk, opt), &WKSTR("true")) || (str_eql(get_str(wk, opt), &WKSTR("if-release")) && str_eql(get_str(wk, buildtype), &WKSTR("release")))) { push_args(wk, args, compilers[t].args.define("NDEBUG")); } get_option_value_for_tgt(wk, proj, tgt, "b_colorout", &opt); if (!str_eql(get_str(wk, opt), &WKSTR("never"))) { push_args(wk, args, compilers[t].args.color_output(get_cstr(wk, opt))); } get_option_value_for_tgt(wk, proj, tgt, "b_lto", &opt); if (get_obj_bool(wk, opt)) { push_args(wk, args, compilers[t].args.enable_lto()); } } static bool get_base_compiler_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, enum compiler_language lang, obj comp_id, obj *res) { struct obj_compiler *comp = get_obj_compiler(wk, comp_id); enum compiler_type t = comp->type; obj args; make_obj(wk, &args, obj_array); get_std_args(wk, proj, tgt, args, lang, t); if (!get_buildtype_args(wk, proj, tgt, args, t)) { return false; } get_warning_args(wk, proj, tgt, args, t); get_werror_args(wk, proj, tgt, args, t); setup_optional_b_args_compiler(wk, proj, tgt, args, t); { /* option args (from option('x_args')) */ get_option_compile_args(wk, proj, tgt, args, lang); } { /* global args */ obj global_args; if (obj_dict_geti(wk, wk->global_args, lang, &global_args)) { obj_array_extend(wk, args, global_args); } } { /* project args */ obj proj_args; if (obj_dict_geti(wk, proj->args, lang, &proj_args)) { obj_array_extend(wk, args, proj_args); } } *res = args; return true; } void setup_compiler_args_includes(struct workspace *wk, obj compiler, obj include_dirs, obj args, bool relativize) { obj_array_foreach(wk, include_dirs, &(struct setup_compiler_args_includes_ctx) { .args = args, .t = get_obj_compiler(wk, compiler)->type, .dont_relativize = !relativize, }, setup_compiler_args_includes_iter); } static enum iteration_result setup_compiler_args_iter(struct workspace *wk, void *_ctx, enum compiler_language lang, obj comp_id) { struct setup_compiler_args_ctx *ctx = _ctx; struct obj_compiler *comp = get_obj_compiler(wk, comp_id); enum compiler_type t = comp->type; obj args; if (!get_base_compiler_args(wk, ctx->proj, ctx->tgt, lang, comp_id, &args)) { return ir_err; } obj inc_dirs; obj_array_dedup(wk, ctx->include_dirs, &inc_dirs); { /* project includes */ obj proj_incs; if (obj_dict_geti(wk, ctx->proj->include_dirs, lang, &proj_incs)) { obj_array_extend(wk, inc_dirs, proj_incs); obj dedupd; obj_array_dedup(wk, inc_dirs, &dedupd); inc_dirs = dedupd; } } setup_compiler_args_includes(wk, comp_id, inc_dirs, args, true); { /* dep args */ if (ctx->dep_args) { obj_array_extend(wk, args, ctx->dep_args); } } { /* target args */ obj tgt_args; if (obj_dict_geti(wk, ctx->tgt->args, lang, &tgt_args) && tgt_args && get_obj_array(wk, tgt_args)->len) { obj_array_extend(wk, args, tgt_args); } } if (ctx->tgt->flags & build_tgt_flag_pic) { push_args(wk, args, compilers[t].args.pic()); } if (ctx->tgt->flags & build_tgt_flag_pie) { push_args(wk, args, compilers[t].args.pie()); } if (ctx->tgt->flags & build_tgt_flag_visibility) { push_args(wk, args, compilers[t].args.visibility(ctx->tgt->visibility)); } obj_dict_seti(wk, ctx->joined_args, lang, join_args_shell_ninja(wk, args)); return ir_cont; } bool setup_compiler_args(struct workspace *wk, const struct obj_build_target *tgt, const struct project *proj, obj include_dirs, obj dep_args, obj *joined_args) { make_obj(wk, joined_args, obj_dict); struct setup_compiler_args_ctx ctx = { .tgt = tgt, .proj = proj, .include_dirs = include_dirs, .dep_args = dep_args, .joined_args = *joined_args, }; if (!obj_dict_foreach(wk, proj->compilers, &ctx, setup_compiler_args_iter)) { return false; } return true; } bool build_target_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj *joined_args) { struct build_dep args = tgt->dep_internal; if (tgt->flags & build_tgt_generated_include) { const char *private_path = get_cstr(wk, tgt->private_path); // mkdir so that the include dir doesn't get pruned later on if (!fs_mkdir_p(private_path)) { return false; } obj inc; make_obj(wk, &inc, obj_array); obj_array_push(wk, inc, make_str(wk, private_path)); obj_array_extend_nodup(wk, inc, args.include_directories); args.include_directories = inc; } if (!setup_compiler_args(wk, tgt, proj, args.include_directories, args.compile_args, joined_args)) { return false; } return true; } void get_option_link_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args_id, enum compiler_language lang) { obj args; switch (lang) { case compiler_language_c: get_option_value_for_tgt(wk, proj, tgt, "c_link_args", &args); break; case compiler_language_cpp: get_option_value_for_tgt(wk, proj, tgt, "cpp_args", &args); break; default: return; } obj_array_extend(wk, args_id, args); } static enum iteration_result process_rpath_iter(struct workspace *wk, void *_ctx, obj v) { struct setup_linker_args_ctx *ctx = _ctx; if (!get_str(wk, v)->len) { return ir_cont; } push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.rpath(get_cstr(wk, v))); return ir_cont; } static bool setup_optional_b_args_linker(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, obj args, enum linker_type t) { #ifndef MUON_BOOTSTRAPPED // If we aren't bootstrapped, we don't yet have any b_ options defined return true; #endif obj opt; get_option_value_for_tgt(wk, proj, tgt, "b_pgo", &opt); if (!str_eql(get_str(wk, opt), &WKSTR("off"))) { uint32_t stage; const struct str *sl = get_str(wk, opt); if (str_eql(sl, &WKSTR("generate"))) { stage = 0; } else if (str_eql(sl, &WKSTR("use"))) { stage = 1; } else { UNREACHABLE; return false; } push_args(wk, args, linkers[t].args.pgo(stage)); } get_option_value_for_tgt(wk, proj, tgt, "b_sanitize", &opt); if (strcmp(get_cstr(wk, opt), "none") != 0) { push_args(wk, args, linkers[t].args.sanitize(get_cstr(wk, opt))); } get_option_value_for_tgt(wk, proj, tgt, "b_lto", &opt); if (get_obj_bool(wk, opt)) { push_args(wk, args, linkers[t].args.enable_lto()); } return true; } static enum iteration_result push_not_found_lib_iter(struct workspace *wk, void *_ctx, obj v) { struct setup_linker_args_ctx *ctx = _ctx; push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.lib(get_cstr(wk, v))); return ir_cont; } void setup_linker_args(struct workspace *wk, const struct project *proj, const struct obj_build_target *tgt, struct setup_linker_args_ctx *ctx) { obj link_with; obj_array_dedup(wk, ctx->args->link_with, &link_with); ctx->args->link_with = link_with; obj link_whole; obj_array_dedup(wk, ctx->args->link_whole, &link_whole); ctx->args->link_whole = link_whole; obj link_with_not_found; obj_array_dedup(wk, ctx->args->link_with_not_found, &link_with_not_found); ctx->args->link_with_not_found = link_with_not_found; push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.as_needed()); if (proj) { assert(tgt); if (!(tgt->type & tgt_shared_module)) { push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.no_undefined()); } if (tgt->flags & build_tgt_flag_export_dynamic) { push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.export_dynamic()); } setup_optional_b_args_linker(wk, proj, tgt, ctx->args->link_args, ctx->linker); { /* option args (from option('x_link_args')) */ get_option_link_args(wk, proj, tgt, ctx->args->link_args, ctx->link_lang); } /* global args */ obj global_args; if (obj_dict_geti(wk, wk->global_link_args, ctx->link_lang, &global_args)) { obj_array_extend(wk, ctx->args->link_args, global_args); } /* project args */ obj proj_args; if (obj_dict_geti(wk, proj->link_args, ctx->link_lang, &proj_args)) { obj_array_extend(wk, ctx->args->link_args, proj_args); } } obj_array_foreach(wk, ctx->args->rpath, ctx, process_rpath_iter); bool have_link_whole = get_obj_array(wk, ctx->args->link_whole)->len, have_link_with = have_link_whole || get_obj_array(wk, ctx->args->link_with)->len || get_obj_array(wk, ctx->args->link_with_not_found)->len; if (have_link_with) { push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.start_group()); if (have_link_whole) { push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.whole_archive()); obj_array_extend(wk, ctx->args->link_args, ctx->args->link_whole); push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.no_whole_archive()); } obj_array_extend(wk, ctx->args->link_args, ctx->args->link_with); obj_array_foreach(wk, ctx->args->link_with_not_found, ctx, push_not_found_lib_iter); push_args(wk, ctx->args->link_args, linkers[ctx->linker].args.end_group()); } } /* */ struct relativize_paths_ctx { bool relativize_strings; obj *oneshot; obj dest; }; static enum iteration_result relativize_paths_iter(struct workspace *wk, void *_ctx, obj val) { struct relativize_paths_ctx *ctx = _ctx; const char *str; if (get_obj_type(wk, val) == obj_string) { if (ctx->relativize_strings) { str = get_cstr(wk, val); } else { if (ctx->oneshot) { *ctx->oneshot = val; } else { obj_array_push(wk, ctx->dest, val); } return ir_cont; } } else { str = get_file_path(wk, val); } SBUF(buf); path_relative_to(wk, &buf, wk->build_root, str); obj s = sbuf_into_str(wk, &buf); if (ctx->oneshot) { *ctx->oneshot = s; } else { obj_array_push(wk, ctx->dest, s); } return ir_cont; } void relativize_paths(struct workspace *wk, obj arr, bool relativize_strings, obj *res) { make_obj(wk, res, obj_array); struct relativize_paths_ctx ctx = { .relativize_strings = relativize_strings, .dest = *res, }; obj_array_foreach(wk, arr, &ctx, relativize_paths_iter); } void relativize_path(struct workspace *wk, obj path, bool relativize_strings, obj *res) { make_obj(wk, res, obj_array); struct relativize_paths_ctx ctx = { .relativize_strings = relativize_strings, .oneshot = res, }; relativize_paths_iter(wk, &ctx, path); } void relativize_path_push(struct workspace *wk, obj path, obj arr) { struct relativize_paths_ctx ctx = { .dest = arr, }; relativize_paths_iter(wk, &ctx, path); } muon-0.2.0/src/backend/ninja.c000066400000000000000000000226651441402162300161200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "backend/ninja.h" #include "backend/ninja/alias_target.h" #include "backend/ninja/build_target.h" #include "backend/ninja/custom_target.h" #include "backend/ninja/rules.h" #include "backend/output.h" #include "error.h" #include "external/samurai.h" #include "lang/serial.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "tracy.h" struct check_tgt_ctx { bool need_phony; }; static enum iteration_result check_tgt_iter(struct workspace *wk, void *_ctx, obj tgt_id) { struct check_tgt_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, tgt_id); switch (t) { case obj_custom_target: { struct obj_custom_target *tgt = get_obj_custom_target(wk, tgt_id); if (tgt->flags & custom_target_build_always_stale) { ctx->need_phony = true; } break; } case obj_alias_target: case obj_build_target: case obj_both_libs: break; default: LOG_E("invalid tgt type '%s'", obj_type_to_s(t)); return ir_err; } return ir_cont; } static enum iteration_result write_tgt_iter(struct workspace *wk, void *_ctx, obj tgt_id) { TracyCZoneAutoS; enum iteration_result ret; struct write_tgt_ctx *ctx = _ctx; struct obj_clear_mark mk; obj_set_clear_mark(wk, &mk); const char *name = NULL; enum obj_type t = get_obj_type(wk, tgt_id); switch (t) { case obj_alias_target: ret = ninja_write_alias_tgt(wk, tgt_id, ctx); name = get_cstr(wk, get_obj_alias_target(wk, tgt_id)->name); break; case obj_both_libs: tgt_id = get_obj_both_libs(wk, tgt_id)->dynamic_lib; /* fallthrough */ case obj_build_target: ret = ninja_write_build_tgt(wk, tgt_id, ctx); name = get_cstr(wk, get_obj_build_target(wk, tgt_id)->build_name); break; case obj_custom_target: ret = ninja_write_custom_tgt(wk, tgt_id, ctx); name = get_cstr(wk, get_obj_custom_target(wk, tgt_id)->name); break; default: LOG_E("invalid tgt type '%s'", obj_type_to_s(t)); ret = ir_err; break; } if (!ret) { LOG_E("failed to write %s '%s'", obj_type_to_s(t), name); } obj_clear(wk, &mk); TracyCZoneAutoE; return ret; } struct write_build_ctx { obj compiler_rule_arr; }; static bool ninja_write_build(struct workspace *wk, void *_ctx, FILE *out) { struct write_build_ctx *ctx = _ctx; struct check_tgt_ctx check_ctx = { 0 }; uint32_t i; for (i = 0; i < wk->projects.len; ++i) { struct project *proj = darr_get(&wk->projects, i); if (proj->not_ok) { continue; } obj_array_foreach(wk, proj->targets, &check_ctx, check_tgt_iter); } if (!ninja_write_rules(out, wk, darr_get(&wk->projects, 0), check_ctx.need_phony, ctx->compiler_rule_arr)) { return false; } bool wrote_default = false; for (i = 0; i < wk->projects.len; ++i) { struct project *proj = darr_get(&wk->projects, i); if (proj->not_ok) { continue; } struct write_tgt_ctx ctx = { .out = out, .proj = proj }; if (!obj_array_foreach(wk, proj->targets, &ctx, write_tgt_iter)) { LOG_E("failed to write rules for project %s", get_cstr(wk, proj->cfg.name)); return false; } wrote_default |= ctx.wrote_default; } if (!wrote_default) { fprintf(out, "build muon_do_nothing: phony\n" "default muon_do_nothing\n" ); } return true; } static bool ninja_write_tests(struct workspace *wk, void *_ctx, FILE *out) { bool wrote_header = false; obj tests; make_obj(wk, &tests, obj_dict); uint32_t i; for (i = 0; i < wk->projects.len; ++i) { struct project *proj = darr_get(&wk->projects, i); if (proj->not_ok) { continue; } if (proj->tests && get_obj_array(wk, proj->tests)->len) { if (!wrote_header) { L("writing tests"); wrote_header = true; } obj res, key; key = proj->cfg.name; if (obj_dict_index(wk, tests, key, &res)) { assert(false && "project defined multiple times"); } obj arr; make_obj(wk, &arr, obj_array); obj_array_push(wk, arr, proj->tests); obj_array_push(wk, arr, proj->test_setups); obj_dict_set(wk, tests, key, arr); } } return serial_dump(wk, tests, out); } static bool ninja_write_install(struct workspace *wk, void *_ctx, FILE *out) { obj o; make_obj(wk, &o, obj_array); obj_array_push(wk, o, wk->install); obj_array_push(wk, o, wk->install_scripts); obj_array_push(wk, o, make_str(wk, wk->source_root)); struct project *proj = darr_get(&wk->projects, 0); obj prefix; get_option_value(wk, proj, "prefix", &prefix); obj_array_push(wk, o, prefix); return serial_dump(wk, o, out); } static bool ninja_write_compiler_check_cache(struct workspace *wk, void *_ctx, FILE *out) { return serial_dump(wk, wk->compiler_check_cache, out); } static bool ninja_write_summary_file(struct workspace *wk, void *_ctx, FILE *out) { workspace_print_summaries(wk, out); return true; } static bool ninja_write_option_info(struct workspace *wk, void *_ctx, FILE *out) { obj arr; make_obj(wk, &arr, obj_array); obj_array_push(wk, arr, wk->global_opts); struct project *main_proj = darr_get(&wk->projects, 0); obj_array_push(wk, arr, main_proj->opts); return serial_dump(wk, arr, out); } bool ninja_write_all(struct workspace *wk) { struct write_build_ctx ctx = { 0 }; make_obj(wk, &ctx.compiler_rule_arr, obj_array); if (!(with_open(wk->build_root, "build.ninja", wk, &ctx, ninja_write_build) && with_open(wk->muon_private, output_path.tests, wk, NULL, ninja_write_tests) && with_open(wk->muon_private, output_path.install, wk, NULL, ninja_write_install) && with_open(wk->muon_private, output_path.compiler_check_cache, wk, NULL, ninja_write_compiler_check_cache) && with_open(wk->muon_private, output_path.summary, wk, NULL, ninja_write_summary_file) && with_open(wk->muon_private, output_path.option_info, wk, NULL, ninja_write_option_info) )) { return false; } {/* compile_commands.json */ TracyCZoneN(tctx_compdb, "output compile_commands.json", true); obj compdb_args; make_obj(wk, &compdb_args, obj_array); obj_array_push(wk, compdb_args, make_str(wk, "-C")); obj_array_push(wk, compdb_args, make_str(wk, wk->build_root)); obj_array_push(wk, compdb_args, make_str(wk, "-t")); obj_array_push(wk, compdb_args, make_str(wk, "compdb")); obj_array_extend_nodup(wk, compdb_args, ctx.compiler_rule_arr); if (ninja_run(wk, compdb_args, wk->build_root, "compile_commands.json") != 0) { LOG_E("error writing compile_commands.json"); } TracyCZoneEnd(tctx_compdb); } return true; } int ninja_run(struct workspace *wk, obj args, const char *chdir, const char *capture) { // XXX since samu was designed to be an executable and not a library, // lots of the resource management is left to the OS. For instance, // there are several important globals that are assumed to be // zero-initialized. Not to mention memory "leaks". This is all fine // since almost zero effort has been put in to making samu into a true // libsamu, however it means that calling the internal samu more than // once is riddled with UB. Prevent that with this hacky static // variable by falling back to executing an external ninja-compatible // tool if the internal samu has already been invoked. static bool internal_samu_has_been_called = false; const char *argstr; uint32_t argstr_argc; int ret = 1; char *const *argv = NULL; uint32_t argc; SBUF_manual(cwd); if (chdir) { path_cwd(NULL, &cwd); if (!path_chdir(chdir)) { goto ret; } } if (have_samurai && !internal_samu_has_been_called) { internal_samu_has_been_called = true; join_args_argstr(wk, &argstr, &argstr_argc, args); argc = argstr_to_argv(argstr, argstr_argc, "samu", &argv); int old_stdout; if (capture) { if (!fs_redirect(capture, "wb", STDOUT_FILENO, &old_stdout)) { goto ret; } } bool res = muon_samu(argc, argv); if (capture) { if (!fs_redirect_restore(STDOUT_FILENO, old_stdout)) { goto ret; } } ret = res ? 0 : 1; } else { struct run_cmd_ctx cmd_ctx = { 0 }; SBUF_manual(cmd); const char *prepend = NULL; obj ninja_opt_id; if (!get_option(wk, NULL, &WKSTR("env.NINJA"), &ninja_opt_id)) { UNREACHABLE; } const struct obj_option *ninja_opt = get_obj_option(wk, ninja_opt_id); if (ninja_opt->source == option_value_source_default && have_samurai) { obj cmd_arr; make_obj(wk, &cmd_arr, obj_array); obj_array_push(wk, cmd_arr, make_str(wk, wk->argv0)); obj_array_push(wk, cmd_arr, make_str(wk, "samu")); obj_array_extend_nodup(wk, cmd_arr, args); args = cmd_arr; } else if (ninja_opt->source == option_value_source_default && (fs_find_cmd(NULL, &cmd, "samu") || fs_find_cmd(NULL, &cmd, "ninja"))) { prepend = cmd.buf; } else { obj cmd_arr; obj_array_dup(wk, ninja_opt->val, &cmd_arr); obj_array_extend_nodup(wk, cmd_arr, args); args = cmd_arr; } join_args_argstr(wk, &argstr, &argstr_argc, args); argc = argstr_to_argv(argstr, argstr_argc, prepend, &argv); if (!capture) { cmd_ctx.flags |= run_cmd_ctx_flag_dont_capture; } if (!run_cmd_argv(&cmd_ctx, argv, NULL, 0)) { LOG_E("%s", cmd_ctx.err_msg); goto run_cmd_done; } if (capture) { if (!fs_write(capture, (uint8_t *)cmd_ctx.out.buf, cmd_ctx.out.len)) { goto run_cmd_done; } } ret = cmd_ctx.status; run_cmd_done: sbuf_destroy(&cmd); run_cmd_ctx_destroy(&cmd_ctx); } ret: if (argv) { z_free((void *)argv); } if (chdir) { path_chdir(cwd.buf); } sbuf_destroy(&cwd); return ret; } muon-0.2.0/src/backend/ninja/000077500000000000000000000000001441402162300157415ustar00rootroot00000000000000muon-0.2.0/src/backend/ninja/alias_target.c000066400000000000000000000020251441402162300205430ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: dffdff2423 * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "args.h" #include "backend/ninja.h" #include "backend/ninja/alias_target.h" #include "lang/object.h" #include "lang/string.h" #include "lang/workspace.h" #include "log.h" bool ninja_write_alias_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx) { struct obj_alias_target *tgt = get_obj_alias_target(wk, tgt_id); L("writing rules for alias target '%s'", get_cstr(wk, tgt->name)); SBUF(name_esc); ninja_escape(wk, &name_esc, get_cstr(wk, tgt->name)); obj depstrs; if (!arr_to_args(wk, arr_to_args_alias_target | arr_to_args_build_target | arr_to_args_custom_target | arr_to_args_relativize_paths, tgt->depends, &depstrs)) { return false; } obj depstr = join_args_ninja(wk, depstrs); if (fprintf(ctx->out, "build %s: phony | %s\n\n", name_esc.buf, get_cstr(wk, depstr)) < 0) { return false; } return true; } muon-0.2.0/src/backend/ninja/build_target.c000066400000000000000000000155431441402162300205620ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "backend/common_args.h" #include "backend/ninja.h" #include "backend/ninja/build_target.h" #include "error.h" #include "functions/build_target.h" #include "functions/kernel/dependency.h" #include "lang/workspace.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" struct write_tgt_iter_ctx { FILE *out; const struct obj_build_target *tgt; const struct project *proj; struct build_dep args; obj joined_args; obj object_names; obj order_deps; obj implicit_deps; bool have_order_deps; bool have_link_language; }; static enum iteration_result add_tgt_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct write_tgt_iter_ctx *ctx = _ctx; const char *src = get_file_path(wk, val); SBUF(path); path_relative_to(wk, &path, wk->build_root, src); obj_array_push(wk, ctx->object_names, sbuf_into_str(wk, &path)); return ir_cont; } static enum iteration_result write_tgt_sources_iter(struct workspace *wk, void *_ctx, obj val) { struct write_tgt_iter_ctx *ctx = _ctx; const char *src = get_file_path(wk, val); enum compiler_language lang; if (!filename_to_compiler_language(src, &lang)) { UNREACHABLE; } /* build paths */ SBUF(dest_path); if (!tgt_src_to_object_path(wk, ctx->tgt, val, true, &dest_path)) { return ir_err; } SBUF(src_path); path_relative_to(wk, &src_path, wk->build_root, src); obj_array_push(wk, ctx->object_names, sbuf_into_str(wk, &dest_path)); /* build rules and args */ obj rule_name, specialized_rule; { obj rule_name_arr; if (!obj_dict_geti(wk, ctx->tgt->required_compilers, lang, &rule_name_arr)) { UNREACHABLE; } obj_array_index(wk, rule_name_arr, 0, &rule_name); obj_array_index(wk, rule_name_arr, 1, &specialized_rule); if (!specialized_rule) { if (!ctx->joined_args && !build_target_args(wk, ctx->proj, ctx->tgt, &ctx->joined_args)) { return ir_err; } } } SBUF(esc_dest_path); SBUF(esc_path); ninja_escape(wk, &esc_dest_path, dest_path.buf); ninja_escape(wk, &esc_path, src_path.buf); fprintf(ctx->out, "build %s: %s %s", esc_dest_path.buf, get_cstr(wk, rule_name), esc_path.buf); if (ctx->implicit_deps) { fputs(" | ", ctx->out); fputs(get_cstr(wk, ctx->implicit_deps), ctx->out); } if (ctx->have_order_deps) { fprintf(ctx->out, " || %s", get_cstr(wk, ctx->order_deps)); } fputc('\n', ctx->out); if (!specialized_rule) { obj args; if (!obj_dict_geti(wk, ctx->joined_args, lang, &args)) { UNREACHABLE; } fprintf(ctx->out, " ARGS = %s\n", get_cstr(wk, args)); } return ir_cont; } bool ninja_write_build_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *wctx) { struct obj_build_target *tgt = get_obj_build_target(wk, tgt_id); L("writing rules for target '%s'", get_cstr(wk, tgt->build_name)); SBUF(esc_path); { SBUF(rel_build_path); path_relative_to(wk, &rel_build_path, wk->build_root, get_cstr(wk, tgt->build_path)); ninja_escape(wk, &esc_path, rel_build_path.buf); } struct write_tgt_iter_ctx ctx = { .tgt = tgt, .proj = wctx->proj, .out = wctx->out, }; enum linker_type linker; { /* determine linker */ obj comp_id; if (!obj_dict_geti(wk, ctx.proj->compilers, tgt->dep_internal.link_language, &comp_id)) { LOG_E("no compiler defined for language %s", compiler_language_to_s(tgt->dep_internal.link_language)); return false; } linker = compilers[get_obj_compiler(wk, comp_id)->type].linker; } make_obj(wk, &ctx.object_names, obj_array); ctx.args = tgt->dep_internal; relativize_paths(wk, ctx.args.link_with, true, &ctx.args.link_with); relativize_paths(wk, ctx.args.link_whole, true, &ctx.args.link_whole); { /* order deps */ if ((ctx.have_order_deps = (get_obj_array(wk, ctx.args.order_deps)->len > 0))) { obj deduped; obj_array_dedup(wk, ctx.args.order_deps, &deduped); obj order_deps = join_args_ninja(wk, deduped); if (get_obj_array(wk, deduped)->len > 1) { fprintf(wctx->out, "build %s-order_deps: phony || %s\n", esc_path.buf, get_cstr(wk, order_deps)); ctx.have_order_deps = false; ctx.implicit_deps = make_strf(wk, "%s-order_deps", esc_path.buf); } else { ctx.order_deps = order_deps; } } } { /* sources */ obj_array_foreach(wk, tgt->objects, &ctx, add_tgt_objects_iter); if (!obj_array_foreach(wk, tgt->src, &ctx, write_tgt_sources_iter)) { return false; } } obj implicit_link_deps; make_obj(wk, &implicit_link_deps, obj_array); if (ctx.implicit_deps) { obj_array_push(wk, implicit_link_deps, ctx.implicit_deps); } if (!(tgt->type & (tgt_static_library))) { struct setup_linker_args_ctx sctx = { .linker = linker, .link_lang = tgt->dep_internal.link_language, .args = &ctx.args }; setup_linker_args(wk, ctx.proj, tgt, &sctx); if (get_obj_array(wk, ctx.args.link_with)->len) { obj_array_extend(wk, implicit_link_deps, ctx.args.link_with); } if (get_obj_array(wk, ctx.args.link_whole)->len) { obj_array_extend(wk, implicit_link_deps, ctx.args.link_whole); } } if (tgt->link_depends) { obj arr; if (!arr_to_args(wk, arr_to_args_relativize_paths, tgt->link_depends, &arr)) { return false; } obj_array_extend_nodup(wk, implicit_link_deps, arr); } if (tgt->type & (tgt_dynamic_library | tgt_shared_module)) { push_args(wk, ctx.args.link_args, linkers[linker].args.shared()); push_args(wk, ctx.args.link_args, linkers[linker].args.soname(get_cstr(wk, tgt->soname))); if (tgt->type == tgt_shared_module) { push_args(wk, ctx.args.link_args, linkers[linker].args.allow_shlib_undefined()); } } const char *linker_type, *link_args; bool linker_rule_prefix = false; switch (tgt->type) { case tgt_shared_module: case tgt_dynamic_library: case tgt_executable: linker_type = compiler_language_to_s(tgt->dep_internal.link_language); linker_rule_prefix = true; link_args = get_cstr(wk, join_args_shell_ninja(wk, ctx.args.link_args)); break; case tgt_static_library: linker_type = "static"; link_args = ar_arguments(); break; default: assert(false); return false; } fprintf(wctx->out, "build %s: %s%s%s_linker ", esc_path.buf, linker_rule_prefix ? get_cstr(wk, ctx.proj->rule_prefix) : "", linker_rule_prefix ? "_" : "", linker_type); fputs(get_cstr(wk, join_args_ninja(wk, ctx.object_names)), wctx->out); if (get_obj_array(wk, implicit_link_deps)->len) { implicit_link_deps = join_args_ninja(wk, implicit_link_deps); fputs(" | ", wctx->out); fputs(get_cstr(wk, implicit_link_deps), wctx->out); } if (ctx.have_order_deps) { fputs(" || ", wctx->out); fputs(get_cstr(wk, ctx.order_deps), wctx->out); } fprintf(wctx->out, "\n LINK_ARGS = %s\n", link_args); if (tgt->flags & build_tgt_flag_build_by_default) { wctx->wrote_default = true; fprintf(wctx->out, "default %s\n", esc_path.buf); } fprintf(wctx->out, "\n"); return true; } muon-0.2.0/src/backend/ninja/custom_target.c000066400000000000000000000115241441402162300207700ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "backend/common_args.h" #include "backend/ninja.h" #include "backend/ninja/custom_target.h" #include "lang/serial.h" #include "lang/workspace.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" // appended to custom_target data files to make them unique static uint32_t custom_tgt_dat_sequence = 0; static enum iteration_result ninja_args_are_escapable_iter(struct workspace *wk, void *_ctx, obj v) { const struct str *ss = get_str(wk, v); if (str_has_null(ss)) { return ir_err; } if (strchr(ss->s, '\n')) { return ir_err; } return ir_cont; } static bool ninja_args_are_escapable(struct workspace *wk, obj arr) { return obj_array_foreach(wk, arr, NULL, ninja_args_are_escapable_iter); } static bool write_custom_target_dat(struct workspace *wk, struct obj_custom_target *tgt, obj data_obj, const char *dir, obj *res) { assert(tgt->name && "unnamed targets cannot have a custom data"); SBUF(name); sbuf_pushf(wk, &name, "%s%d.dat", get_cstr(wk, tgt->name), custom_tgt_dat_sequence); ++custom_tgt_dat_sequence; SBUF(dirpath); SBUF(dat_path); path_join(wk, &dirpath, wk->muon_private, dir); path_join(wk, &dat_path, dirpath.buf, name.buf); FILE *dat; if (!fs_mkdir_p(dirpath.buf)) { return false; } else if (!(dat = fs_fopen(dat_path.buf, "wb"))) { return false; } else if (!serial_dump(wk, data_obj, dat)) { return false; } else if (!fs_fclose(dat)) { return false; } *res = make_str(wk, dat_path.buf); return true; } bool ninja_write_custom_tgt(struct workspace *wk, obj tgt_id, struct write_tgt_ctx *ctx) { struct obj_custom_target *tgt = get_obj_custom_target(wk, tgt_id); L("writing rules for custom target '%s'", get_cstr(wk, tgt->name)); obj outputs, inputs = 0, cmdline; if (tgt->input) { relativize_paths(wk, tgt->input, false, &inputs); } make_obj(wk, &outputs, obj_array); if (tgt->output) { relativize_paths(wk, tgt->output, false, &outputs); } else { assert(tgt->name && "unnamed targets cannot have no output"); obj name; if (ctx->proj->subproject_name) { name = make_strf(wk, "%s@@%s", get_cstr(wk, ctx->proj->subproject_name), get_cstr(wk, tgt->name)); } else { name = tgt->name; } obj_array_push(wk, outputs, name); } make_obj(wk, &cmdline, obj_array); obj_array_push(wk, cmdline, make_str(wk, wk->argv0)); obj_array_push(wk, cmdline, make_str(wk, "internal")); obj_array_push(wk, cmdline, make_str(wk, "exe")); if (tgt->flags & custom_target_capture) { obj_array_push(wk, cmdline, make_str(wk, "-c")); obj elem; obj_array_index(wk, tgt->output, 0, &elem); relativize_path_push(wk, elem, cmdline); } if (tgt->flags & custom_target_feed) { obj_array_push(wk, cmdline, make_str(wk, "-f")); obj elem; obj_array_index(wk, tgt->input, 0, &elem); relativize_path_push(wk, elem, cmdline); } if (tgt->env) { obj env_dat_path; if (!write_custom_target_dat(wk, tgt, tgt->env, "custom_tgt_env", &env_dat_path)) { return ir_err; } obj_array_push(wk, cmdline, make_str(wk, "-e")); obj_array_push(wk, cmdline, env_dat_path); } obj tgt_args; if (!arr_to_args(wk, 0, tgt->args, &tgt_args)) { return ir_err; } if (ninja_args_are_escapable(wk, tgt_args)) { obj_array_push(wk, cmdline, make_str(wk, "--")); obj_array_extend_nodup(wk, cmdline, tgt_args); } else { obj args_dat_path; if (!write_custom_target_dat(wk, tgt, tgt_args, "custom_tgt_args", &args_dat_path)) { return ir_err; } obj_array_push(wk, cmdline, make_str(wk, "-a")); obj_array_push(wk, cmdline, args_dat_path); } obj depends_rel; relativize_paths(wk, tgt->depends, false, &depends_rel); if (tgt->flags & custom_target_build_always_stale) { obj_array_push(wk, depends_rel, make_str(wk, "build_always_stale")); } obj depends = join_args_ninja(wk, depends_rel); outputs = join_args_ninja(wk, outputs); inputs = inputs ? join_args_ninja(wk, inputs) : make_str(wk, ""); cmdline = join_args_shell_ninja(wk, cmdline); const char *rule; if (tgt->depfile) { rule = "CUSTOM_COMMAND_DEP"; } else { rule = "CUSTOM_COMMAND"; } fprintf(ctx->out, "build %s: %s %s | %s\n" " COMMAND = %s\n", get_cstr(wk, outputs), rule, get_cstr(wk, inputs), get_cstr(wk, depends), get_cstr(wk, cmdline) ); if (tgt->depfile) { obj depfile_rel; relativize_path(wk, tgt->depfile, false, &depfile_rel); fprintf(ctx->out, " DEPFILE = %s\n", get_cstr(wk, depfile_rel)); } if (tgt->flags & custom_target_console) { fprintf(ctx->out, " pool = console\n"); } if (tgt->flags & custom_target_build_by_default) { ctx->wrote_default = true; fprintf(ctx->out, "default %s\n", get_cstr(wk, outputs)); } fprintf(ctx->out, "\n"); return ir_cont; } muon-0.2.0/src/backend/ninja/rules.c000066400000000000000000000252411441402162300172430ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "backend/common_args.h" #include "backend/ninja/rules.h" #include "backend/output.h" #include "error.h" #include "lang/workspace.h" #include "log.h" #include "platform/path.h" #include "tracy.h" struct write_compiler_rule_ctx { FILE *out; struct project *proj; struct obj_build_target *tgt; obj args, generic_rules; }; static void uniqify_name(struct workspace *wk, obj arr, obj name, obj *res) { uint32_t x = 1; while (obj_array_in(wk, arr, name)) { name = make_strf(wk, "%s%d", get_cstr(wk, name), x); ++x; } obj_array_push(wk, arr, name); *res = name; } static enum iteration_result write_linker_rule_iter(struct workspace *wk, void *_ctx, enum compiler_language l, obj comp_id) { struct write_compiler_rule_ctx *ctx = _ctx; struct obj_compiler *comp = get_obj_compiler(wk, comp_id); enum compiler_type t = comp->type; obj args; make_obj(wk, &args, obj_array); obj_array_extend(wk, args, comp->cmd_arr); obj_array_push(wk, args, make_str(wk, "$ARGS")); push_args(wk, args, compilers[t].args.output("$out")); obj_array_push(wk, args, make_str(wk, "$in")); obj_array_push(wk, args, make_str(wk, "$LINK_ARGS")); obj link_command = join_args_plain(wk, args); fprintf(ctx->out, "rule %s_%s_linker\n" " command = %s\n" " description = linking $out\n\n", get_cstr(wk, ctx->proj->rule_prefix), compiler_language_to_s(l), get_cstr(wk, link_command)); return ir_cont; } static void write_compiler_rule(struct workspace *wk, FILE *out, obj rule_args, obj rule_name, enum compiler_language l, obj comp_id) { struct obj_compiler *comp = get_obj_compiler(wk, comp_id); enum compiler_type t = comp->type; const char *deps = NULL; switch (compilers[t].deps) { case compiler_deps_none: break; case compiler_deps_gcc: deps = "gcc"; break; case compiler_deps_msvc: deps = "msvc"; break; } obj args; make_obj(wk, &args, obj_array); obj_array_extend(wk, args, comp->cmd_arr); obj_array_push(wk, args, rule_args); if (compilers[t].deps) { push_args(wk, args, compilers[t].args.deps("$out", "${out}.d")); } push_args(wk, args, compilers[t].args.output("$out")); push_args(wk, args, compilers[t].args.compile_only()); obj_array_push(wk, args, make_str(wk, "$in")); obj compile_command = join_args_plain(wk, args); fprintf(out, "rule %s\n" " command = %s\n", get_cstr(wk, rule_name), get_cstr(wk, compile_command)); if (compilers[t].deps) { fprintf(out, " deps = %s\n" " depfile = ${out}.d\n", deps); } fprintf(out, " description = compiling %s $out\n\n", compiler_language_to_s(l)); } static enum iteration_result write_compiler_rule_iter(struct workspace *wk, void *_ctx, enum compiler_language l, obj comp_id) { struct write_compiler_rule_ctx *ctx = _ctx; obj rule_name; { obj rule_name_arr; if (!obj_dict_geti(wk, ctx->tgt->required_compilers, l, &rule_name_arr)) { return ir_cont; } obj specialized_rule; obj_array_index(wk, rule_name_arr, 0, &rule_name); obj_array_index(wk, rule_name_arr, 1, &specialized_rule); if (!specialized_rule) { return ir_cont; } } obj rule_args; if (!obj_dict_geti(wk, ctx->args, l, &rule_args)) { UNREACHABLE; } write_compiler_rule(wk, ctx->out, rule_args, rule_name, l, comp_id); return ir_cont; } static enum iteration_result write_compiler_rule_tgt_iter(struct workspace *wk, void *_ctx, obj tgt_id) { struct write_compiler_rule_ctx *ctx = _ctx; enum iteration_result ret = ir_err; if (get_obj_type(wk, tgt_id) != obj_build_target) { return ir_cont; } struct obj_clear_mark mk; obj_set_clear_mark(wk, &mk); ctx->tgt = get_obj_build_target(wk, tgt_id); if (!build_target_args(wk, ctx->proj, ctx->tgt, &ctx->args)) { goto ret; } if (!obj_dict_foreach(wk, ctx->proj->compilers, ctx, write_compiler_rule_iter)) { goto ret; } ret = ir_cont; ret: obj_clear(wk, &mk); return ret; } static enum iteration_result write_generic_compiler_rule_iter(struct workspace *wk, void *_ctx, enum compiler_language l, obj comp_id) { struct write_compiler_rule_ctx *ctx = _ctx; obj rule_name; if (!obj_dict_geti(wk, ctx->generic_rules, l, &rule_name)) { return ir_cont; } write_compiler_rule(wk, ctx->out, make_str(wk, "$ARGS"), rule_name, l, comp_id); return ir_cont; } struct name_compiler_rule_ctx { struct project *proj; struct obj_build_target *tgt; obj rule_prefix_arr; obj compiler_rule_arr; obj generic_rules; }; static enum iteration_result name_compiler_rule_iter(struct workspace *wk, void *_ctx, enum compiler_language l, uint32_t count) { struct name_compiler_rule_ctx *ctx = _ctx; bool specialized_rule = count > 2; obj rule_name; if (specialized_rule) { obj name = make_strf(wk, "%s_%s_compiler_for_%s", get_cstr(wk, ctx->proj->rule_prefix), compiler_language_to_s(l), get_cstr(wk, ctx->tgt->build_name) ); uniqify_name(wk, ctx->compiler_rule_arr, name, &rule_name); } else { if (!obj_dict_geti(wk, ctx->generic_rules, l, &rule_name)) { obj name = make_strf(wk, "%s_%s_compiler", get_cstr(wk, ctx->proj->rule_prefix), compiler_language_to_s(l)); uniqify_name(wk, ctx->compiler_rule_arr, name, &rule_name); obj_dict_seti(wk, ctx->generic_rules, l, rule_name); } } obj arr; make_obj(wk, &arr, obj_array); obj_array_push(wk, arr, rule_name); obj_array_push(wk, arr, specialized_rule); obj_dict_seti(wk, ctx->tgt->required_compilers, l, arr); return ir_cont; } static enum iteration_result name_compiler_rule_tgt_iter(struct workspace *wk, void *_ctx, obj tgt_id) { struct name_compiler_rule_ctx *ctx = _ctx; if (get_obj_type(wk, tgt_id) != obj_build_target) { return ir_cont; } ctx->tgt = get_obj_build_target(wk, tgt_id); if (!obj_dict_foreach(wk, ctx->tgt->required_compilers, ctx, name_compiler_rule_iter)) { return ir_err; } return ir_cont; } static enum iteration_result add_global_opts_set_from_env_iter(struct workspace *wk, void *_ctx, obj key, obj val) { obj regen_args = *(obj *)_ctx; struct obj_option *o = get_obj_option(wk, val); if (o->source != option_value_source_environment) { return ir_cont; } // NOTE: This only handles options of type str or [str], which is okay since // the only options that can be set from the environment are of this // type. // TODO: The current implementation of array stringification would // choke on spaces, etc. const char *sval; switch (get_obj_type(wk, o->val)) { case obj_string: sval = get_cstr(wk, o->val); break; case obj_array: { obj joined; obj_array_join(wk, true, o->val, make_str(wk, ","), &joined); sval = get_cstr(wk, joined); break; } default: UNREACHABLE; } obj_array_push(wk, regen_args, make_strf(wk, "-D%s=%s", get_cstr(wk, o->name), sval)); return ir_cont; } bool ninja_write_rules(FILE *out, struct workspace *wk, struct project *main_proj, bool need_phony, obj compiler_rule_arr) { TracyCZoneAutoS; bool res = false; fprintf( out, "# This is the build file for project \"%s\"\n" "# It is autogenerated by the muon build system.\n" "ninja_required_version = 1.7.1\n\n", get_cstr(wk, main_proj->cfg.name) ); fprintf(out, "rule static_linker\n" " command = rm -f $out && ar $LINK_ARGS $out $in\n" " description = linking static $out\n" "\n" "rule CUSTOM_COMMAND\n" " command = $COMMAND\n" " description = $COMMAND\n" " restat = 1\n" "\n" "rule CUSTOM_COMMAND_DEP\n" " command = $COMMAND\n" " description = $COMMAND\n" " deps = gcc\n" " depfile = $DEPFILE\n" " restat = 1\n" "\n" ); obj regen_args; make_obj(wk, ®en_args, obj_array); obj_array_push(wk, regen_args, make_str(wk, wk->argv0)); obj_array_push(wk, regen_args, make_str(wk, "-C")); obj_array_push(wk, regen_args, make_str(wk, wk->source_root)); obj_array_push(wk, regen_args, make_str(wk, "setup")); SBUF(compiler_check_cache_path); path_join(wk, &compiler_check_cache_path, wk->muon_private, output_path.compiler_check_cache); obj_array_push(wk, regen_args, make_str(wk, "-c")); obj_array_push(wk, regen_args, make_str(wk, compiler_check_cache_path.buf)); obj_dict_foreach(wk, wk->global_opts, ®en_args, add_global_opts_set_from_env_iter); uint32_t i; for (i = 0; i < wk->original_commandline.argc; ++i) { obj_array_push(wk, regen_args, make_str(wk, wk->original_commandline.argv[i])); } obj regen_cmd = join_args_shell(wk, regen_args); fprintf(out, "rule REGENERATE_BUILD\n" " command = %s", get_cstr(wk, regen_cmd)); fputs("\n description = Regenerating build files.\n" " generator = 1\n" "\n", out); obj regenerate_deps_rel; { obj deduped; obj_array_dedup(wk, wk->regenerate_deps, &deduped); relativize_paths(wk, deduped, true, ®enerate_deps_rel); } fprintf(out, "build build.ninja: REGENERATE_BUILD %s\n" " pool = console\n\n", get_cstr(wk, join_args_ninja(wk, regenerate_deps_rel)) ); if (need_phony) { fprintf(out, "build build_always_stale: phony\n\n"); } obj rule_prefix_arr; make_obj(wk, &rule_prefix_arr, obj_array); for (i = 0; i < wk->projects.len; ++i) { struct project *proj = darr_get(&wk->projects, i); if (proj->not_ok) { continue; } TracyCZoneN(tctx_name, "name rules", true); { // determine project rule prefix const char *proj_name = get_cstr(wk, proj->cfg.name); char buf[BUF_SIZE_1k] = { 0 }, *p; strncpy(buf, proj_name, BUF_SIZE_1k - 1); for (p = buf; *p; ++p) { if (*p == '_' || ('a' <= *p && *p <= 'z') || ('A' <= *p && *p <= 'Z') || ('0' <= *p && *p <= '9')) { continue; } *p = '_'; } obj pre = make_str(wk, buf); uniqify_name(wk, rule_prefix_arr, pre, &proj->rule_prefix); } obj generic_rules; make_obj(wk, &generic_rules, obj_dict); { struct name_compiler_rule_ctx ctx = { .proj = proj, .rule_prefix_arr = rule_prefix_arr, .compiler_rule_arr = compiler_rule_arr, .generic_rules = generic_rules, }; if (!obj_array_foreach(wk, proj->targets, &ctx, name_compiler_rule_tgt_iter)) { goto ret; } } TracyCZoneEnd(tctx_name); { TracyCZoneN(tctx_rules, "write rules", true); struct write_compiler_rule_ctx ctx = { .out = out, .proj = proj, .generic_rules = generic_rules, }; struct obj_clear_mark mk; obj_set_clear_mark(wk, &mk); if (!obj_array_foreach(wk, proj->targets, &ctx, write_compiler_rule_tgt_iter)) { goto ret; } if (!obj_dict_foreach(wk, proj->compilers, &ctx, write_generic_compiler_rule_iter)) { goto ret; } if (!obj_dict_foreach(wk, proj->compilers, &ctx, write_linker_rule_iter)) { goto ret; } obj_clear(wk, &mk); TracyCZoneEnd(tctx_rules); } } fprintf(out, "# targets\n\n"); res = true; ret: TracyCZoneAutoE; return res; } muon-0.2.0/src/backend/output.c000066400000000000000000000022701441402162300163470ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "backend/output.h" #include "platform/filesystem.h" #include "platform/path.h" #include "tracy.h" const struct output_path output_path = { .private_dir = "muon-private", .summary = "summary.txt", .tests = "tests.dat", .install = "install.dat", .compiler_check_cache = "compiler_check_cache.dat", .option_info = "option_info.dat", }; FILE * output_open(const char *dir, const char *name) { SBUF_manual(path); path_join(NULL, &path, dir, name); FILE *f = fs_fopen(path.buf, "wb"); sbuf_destroy(&path); return f; } bool with_open(const char *dir, const char *name, struct workspace *wk, void *ctx, with_open_callback cb) { TracyCZone(tctx_func, true); #ifdef TRACY_ENABLE char buf[4096] = { 0 }; snprintf(buf, 4096, "with_open('%s')", name); TracyCZoneName(tctx_func, buf, strlen(buf)); #endif bool ret = false; FILE *out; if (!(out = output_open(dir, name))) { goto ret; } else if (!cb(wk, ctx, out)) { goto ret; } else if (!fs_fclose(out)) { goto ret; } ret = true; ret: TracyCZoneEnd(tctx_func); return ret; } muon-0.2.0/src/cmd_install.c000066400000000000000000000163741441402162300157230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "backend/output.h" #include "buf_size.h" #include "cmd_install.h" #include "functions/environment.h" #include "lang/serial.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/rpath_fixer.h" #include "platform/run_cmd.h" struct copy_subdir_ctx { obj exclude_directories; obj exclude_files; bool has_perm; uint32_t perm; const char *src_base, *dest_base; const char *src_root; struct workspace *wk; }; static enum iteration_result copy_subdir_iter(void *_ctx, const char *path) { struct copy_subdir_ctx *ctx = _ctx; SBUF(src); SBUF(dest); path_join(ctx->wk, &src, ctx->src_base, path); path_join(ctx->wk, &dest, ctx->dest_base, path); SBUF(rel); path_relative_to(ctx->wk, &rel, ctx->src_root, src.buf); obj rel_str = sbuf_into_str(ctx->wk, &rel); if (fs_dir_exists(src.buf)) { if (ctx->exclude_directories && obj_array_in(ctx->wk, ctx->exclude_directories, rel_str)) { LOG_I("skipping dir '%s'", src.buf); return ir_cont; } LOG_I("make dir '%s'", dest.buf); if (!fs_dir_exists(dest.buf)) { if (!fs_mkdir(dest.buf)) { return ir_err; } } struct copy_subdir_ctx new_ctx = { .exclude_directories = ctx->exclude_directories, .exclude_files = ctx->exclude_files, .has_perm = ctx->has_perm, .perm = ctx->perm, .src_root = ctx->src_root, .src_base = src.buf, .dest_base = dest.buf, .wk = ctx->wk, }; if (!fs_dir_foreach(src.buf, &new_ctx, copy_subdir_iter)) { return ir_err; } } else if (fs_symlink_exists(src.buf) || fs_file_exists(src.buf)) { if (ctx->exclude_files && obj_array_in(ctx->wk, ctx->exclude_files, rel_str)) { LOG_I("skipping file '%s'", src.buf); return ir_cont; } LOG_I("install '%s' -> '%s'", src.buf, dest.buf); if (!fs_copy_file(src.buf, dest.buf)) { return ir_err; } } else { LOG_E("unhandled file type '%s'", path); return ir_err; } if (ctx->has_perm && !fs_chmod(dest.buf, ctx->perm)) { return ir_err; } return ir_cont; } struct install_ctx { struct install_options *opts; obj prefix; obj full_prefix; obj destdir; }; static enum iteration_result install_iter(struct workspace *wk, void *_ctx, obj v_id) { struct install_ctx *ctx = _ctx; struct obj_install_target *in = get_obj_install_target(wk, v_id); SBUF(dest_dirname); const char *dest = get_cstr(wk, in->dest), *src = get_cstr(wk, in->src); assert(in->type == install_target_symlink || in->type == install_target_emptydir || path_is_absolute(src)); SBUF(full_dest_dir); if (ctx->destdir) { path_join_absolute(wk, &full_dest_dir, get_cstr(wk, ctx->destdir), dest); dest = full_dest_dir.buf; } switch (in->type) { case install_target_default: LOG_I("install '%s' -> '%s'", src, dest); break; case install_target_subdir: LOG_I("install subdir '%s' -> '%s'", src, dest); break; case install_target_symlink: LOG_I("install symlink '%s' -> '%s'", dest, src); break; case install_target_emptydir: LOG_I("install emptydir '%s'", dest); break; default: abort(); } if (ctx->opts->dry_run) { return ir_cont; } switch (in->type) { case install_target_default: case install_target_symlink: path_dirname(wk, &dest_dirname, dest); if (fs_exists(dest_dirname.buf) && !fs_dir_exists(dest_dirname.buf)) { LOG_E("dest '%s' exists and is not a directory", dest_dirname.buf); return ir_err; } if (!fs_mkdir_p(dest_dirname.buf)) { return ir_err; } if (in->type == install_target_default) { if (fs_dir_exists(src)) { if (!fs_copy_dir(src, dest)) { return ir_err; } } else { if (!fs_copy_file(src, dest)) { return ir_err; } } if (in->build_target) { if (!fix_rpaths(dest, wk->build_root)) { return ir_err; } } } else { if (!fs_make_symlink(src, dest, true)) { return ir_err; } } break; case install_target_subdir: if (!fs_mkdir_p(dest)) { return ir_err; } struct copy_subdir_ctx ctx = { .exclude_directories = in->exclude_directories, .exclude_files = in->exclude_files, .has_perm = in->has_perm, .perm = in->perm, .src_root = src, .src_base = src, .dest_base = dest, .wk = wk, }; if (!fs_dir_foreach(src, &ctx, copy_subdir_iter)) { return ir_err; } break; case install_target_emptydir: if (!fs_mkdir_p(dest)) { return ir_err; } break; default: abort(); } if (in->has_perm && !fs_chmod(dest, in->perm)) { return ir_err; } return ir_cont; } static enum iteration_result install_scripts_iter(struct workspace *wk, void *_ctx, obj v) { struct install_ctx *ctx = _ctx; obj env; make_obj(wk, &env, obj_dict); if (ctx->destdir) { obj_dict_set(wk, env, make_str(wk, "DESTDIR"), ctx->destdir); } obj_dict_set(wk, env, make_str(wk, "MESON_INSTALL_PREFIX"), ctx->prefix); obj_dict_set(wk, env, make_str(wk, "MESON_INSTALL_DESTDIR_PREFIX"), ctx->full_prefix); set_default_environment_vars(wk, env, false); const char *argstr, *envstr; uint32_t argc, envc; env_to_envstr(wk, &envstr, &envc, env); join_args_argstr(wk, &argstr, &argc, v); LOG_I("running install script '%s'", argstr); if (ctx->opts->dry_run) { return ir_cont; } struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { LOG_E("failed to run install script: %s", cmd_ctx.err_msg); goto err; } if (cmd_ctx.status != 0) { LOG_E("install script failed"); LOG_E("stdout: %s", cmd_ctx.out.buf); LOG_E("stderr: %s", cmd_ctx.err.buf); goto err; } run_cmd_ctx_destroy(&cmd_ctx); return ir_cont; err: run_cmd_ctx_destroy(&cmd_ctx); return ir_err; } bool install_run(struct install_options *opts) { bool ret = true; SBUF_manual(install_src); path_join(NULL, &install_src, output_path.private_dir, output_path.install); FILE *f; f = fs_fopen(install_src.buf, "rb"); sbuf_destroy(&install_src); if (!f) { return false; } struct workspace wk; workspace_init_bare(&wk); obj install; if (!serial_load(&wk, &install, f)) { LOG_E("failed to load %s", output_path.install); goto ret; } else if (!fs_fclose(f)) { goto ret; } struct install_ctx ctx = { .opts = opts, }; obj install_targets, install_scripts, source_root; obj_array_index(&wk, install, 0, &install_targets); obj_array_index(&wk, install, 1, &install_scripts); obj_array_index(&wk, install, 2, &source_root); obj_array_index(&wk, install, 3, &ctx.prefix); SBUF(build_root); path_cwd(&wk, &build_root); wk.build_root = get_cstr(&wk, sbuf_into_str(&wk, &build_root)); wk.source_root = get_cstr(&wk, source_root); const char *destdir; if ((destdir = getenv("DESTDIR"))) { SBUF(full_prefix); SBUF(abs_destdir); path_make_absolute(&wk, &abs_destdir, destdir); path_join_absolute(&wk, &full_prefix, abs_destdir.buf, get_cstr(&wk, ctx.prefix)); ctx.full_prefix = sbuf_into_str(&wk, &full_prefix); ctx.destdir = sbuf_into_str(&wk, &abs_destdir); } else { ctx.full_prefix = ctx.prefix; } obj_array_foreach(&wk, install_targets, &ctx, install_iter); obj_array_foreach(&wk, install_scripts, &ctx, install_scripts_iter); ret = true; ret: workspace_destroy_bare(&wk); return ret; } muon-0.2.0/src/cmd_test.c000066400000000000000000000514641441402162300152330ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "args.h" #include "backend/ninja.h" #include "backend/output.h" #include "cmd_test.h" #include "error.h" #include "formats/tap.h" #include "functions/environment.h" #include "lang/serial.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "platform/term.h" enum test_result_status { test_result_status_running, test_result_status_ok, test_result_status_failed, test_result_status_timedout, }; struct test_result { struct run_cmd_ctx cmd_ctx; struct obj_test *test; struct timespec start; float dur, timeout; enum test_result_status status; bool busy; struct { bool have; uint32_t pass, total; } subtests; }; struct run_test_ctx { struct test_options *opts; obj proj_name; obj collected_tests; obj deps; uint32_t proj_i; struct { uint32_t test_i, test_len, error_count; uint32_t total_count, total_error_count, total_expect_fail_count; uint32_t total_skipped; uint32_t term_width; bool term; bool ran_tests; } stats; struct { obj env; obj exclude_suites; obj wrapper; float timeout_multiplier; } setup; struct darr test_results; struct test_result *jobs; uint32_t busy_jobs; bool serial; }; /* * Test labeling and output */ static const char * test_category_label(enum test_category cat) { switch (cat) { case test_category_test: return "test"; case test_category_benchmark: return "benchmark"; default: UNREACHABLE_RETURN; } } static void print_test_result(struct workspace *wk, const struct test_result *res) { const char *name = get_cstr(wk, res->test->name); enum { status_failed, status_should_have_failed, status_ok, status_failed_ok, status_running, status_timedout, } status = status_ok; const char *status_msg[] = { [status_failed] = "fail ", [status_should_have_failed] = "ok* ", [status_ok] = "ok ", [status_failed_ok] = "fail*", [status_running] = "start", [status_timedout] = "timeout", }; switch (res->status) { case test_result_status_running: status = status_running; break; case test_result_status_timedout: status = status_timedout; break; case test_result_status_failed: if (res->test->should_fail) { status = status_should_have_failed; } else { status = status_failed; } break; case test_result_status_ok: if (res->test->should_fail) { status = status_failed_ok; } else { status = status_ok; } break; } const char *suite_str = NULL; uint32_t suites_len = 0; if (res->test->suites) { suites_len = get_obj_array(wk, res->test->suites)->len; if (suites_len == 1) { obj s; obj_array_index(wk, res->test->suites, 0, &s); suite_str = get_cstr(wk, s); } else if (suites_len > 1) { obj s; obj_array_join(wk, true, res->test->suites, make_str(wk, ", "), &s); suite_str = get_cstr(wk, s); } } if (log_clr()) { uint32_t clr[] = { [status_failed] = 31, [status_should_have_failed] = 31, [status_ok] = 32, [status_failed_ok] = 33, [status_running] = 0, [status_timedout] = 31, }; log_plain("[\033[%dm%s\033[0m]", clr[status], status_msg[status]); } else { log_plain("[%s]", status_msg[status]); } if (res->status == test_result_status_running) { log_plain(" "); } else { log_plain(" %6.2fs, ", res->dur); } if (res->subtests.have) { log_plain("%3d/%3d subtests, ", res->subtests.pass, res->subtests.total); } if (suite_str) { if (suites_len > 1) { log_plain("[%s]:", suite_str); } else { log_plain("%s:", suite_str); } } log_plain("%s", name); if (status == status_should_have_failed) { log_plain(" - passing test marked as should_fail"); } } static void print_test_progress(struct workspace *wk, struct run_test_ctx *ctx, const struct test_result *res, bool write_line) { if (res->status != test_result_status_running) { ++ctx->stats.total_count; ++ctx->stats.test_i; if (res->status != test_result_status_ok) { ++ctx->stats.total_error_count; ++ctx->stats.error_count; } } if (!ctx->stats.term && !ctx->opts->verbosity) { if (res->status != test_result_status_running) { char c; switch (res->status) { case test_result_status_failed: c = 'E'; break; case test_result_status_timedout: c = 'T'; break; default: c = '.'; break; } log_plain("%c", c); } return; } else if (ctx->stats.term) { log_plain("\r"); } if (write_line && (ctx->opts->verbosity > 0 || res->test->verbose)) { print_test_result(wk, res); if (ctx->stats.term) { log_plain("\033[K"); } log_plain("\n"); } if (!ctx->stats.term) { return; } uint32_t i, pad = 2; char info[BUF_SIZE_4k]; pad += snprintf(info, BUF_SIZE_4k, "%d/%d f: %d (%d) ", ctx->stats.test_i, ctx->stats.test_len, ctx->stats.error_count, ctx->busy_jobs); log_plain("%s[", info); uint32_t pct = (float)(ctx->stats.test_i) * (float)(ctx->stats.term_width - pad) / (float)ctx->stats.test_len; for (i = 0; i < ctx->stats.term_width - pad; ++i) { if (i < pct) { log_plain("="); } else if (i == pct) { log_plain(">"); } else { log_plain(" "); } } log_plain("]"); } /* * test setup / suites */ static bool project_namespaced_name_matches(const char *name1, bool proj2_is_main, const struct str *proj2, const struct str *name2) { struct str proj1 = { 0 }; const char *sep; if ((sep = strchr(name1, ':'))) { proj1 = (struct str){ .s = name1, .len = sep - name1 }; name1 = sep + 1; } if (proj1.len) { if (!str_eql(&proj1, proj2)) { return false; } } else { if (!proj2_is_main) { return false; } } return str_eql(&WKSTR(name1), name2); } struct test_in_suite_ctx { struct run_test_ctx *run_test_ctx; bool found; obj suite; }; static enum iteration_result test_in_suite_iter(struct workspace *wk, void *_ctx, obj s) { struct test_in_suite_ctx *ctx = _ctx; uint32_t i; struct test_options *opts = ctx->run_test_ctx->opts; for (i = 0; i < opts->suites_len; ++i) { if (!project_namespaced_name_matches(opts->suites[i], ctx->run_test_ctx->proj_i == 0, get_str(wk, ctx->run_test_ctx->proj_name), get_str(wk, s))) { continue; } ctx->found = true; return ir_done; } return ir_cont; } static enum iteration_result test_in_exclude_suites_exclude_suites_iter(struct workspace *wk, void *_ctx, obj exclude) { struct test_in_suite_ctx *ctx = _ctx; if (project_namespaced_name_matches(get_cstr(wk, exclude), ctx->run_test_ctx->proj_i == 0, get_str(wk, ctx->run_test_ctx->proj_name), get_str(wk, ctx->suite))) { ctx->found = true; return ir_done; } return ir_cont; } static enum iteration_result test_in_exclude_suites_iter(struct workspace *wk, void *_ctx, obj suite) { struct test_in_suite_ctx *ctx = _ctx; ctx->suite = suite; obj_array_foreach(wk, ctx->run_test_ctx->setup.exclude_suites, ctx, test_in_exclude_suites_exclude_suites_iter); if (ctx->found) { return ir_done; } return ir_cont; } static bool test_in_suite(struct workspace *wk, obj suites, struct run_test_ctx *run_test_ctx) { struct test_in_suite_ctx ctx = { .run_test_ctx = run_test_ctx, }; if (!run_test_ctx->opts->suites_len) { // no suites given on command line if (run_test_ctx->setup.exclude_suites) { obj_array_foreach(wk, suites, &ctx, test_in_exclude_suites_iter); return !ctx.found; } else { return true; } } else if (!suites) { // suites given on command line, but test has no suites return false; } obj_array_foreach(wk, suites, &ctx, test_in_suite_iter); return ctx.found; } struct find_test_setup_ctx { struct run_test_ctx *rtctx; bool found; }; static enum iteration_result find_test_setup_iter(struct workspace *wk, void *_ctx, obj arr) { struct find_test_setup_ctx *ctx = _ctx; /* [name, env, exclude_suites, exe_wrapper, is_default, timeout_multiplier] */ obj name, env, exclude_suites, exe_wrapper, is_default, timeout_multiplier; obj_array_index(wk, arr, 0, &name); obj_array_index(wk, arr, 1, &env); obj_array_index(wk, arr, 2, &exclude_suites); obj_array_index(wk, arr, 3, &exe_wrapper); obj_array_index(wk, arr, 4, &is_default); obj_array_index(wk, arr, 5, &timeout_multiplier); if (ctx->rtctx->opts->setup) { if (!project_namespaced_name_matches(ctx->rtctx->opts->setup, ctx->rtctx->proj_i == 0, get_str(wk, ctx->rtctx->proj_name), get_str(wk, name))) { return ir_cont; } } else if (!is_default || !get_obj_bool(wk, is_default)) { return ir_cont; } if (ctx->rtctx->opts->setup) { L("using test setup '%s'", ctx->rtctx->opts->setup); } else { L("using default test setup '%s'", get_cstr(wk, name)); } ctx->rtctx->setup.env = env; ctx->rtctx->setup.exclude_suites = exclude_suites; ctx->rtctx->setup.wrapper = exe_wrapper; ctx->rtctx->setup.timeout_multiplier = timeout_multiplier ? get_obj_number(wk, timeout_multiplier) : 1.0f; ctx->found = true; return ir_done; } static enum iteration_result find_test_setup_project_iter(struct workspace *wk, void *_ctx, obj project_name, obj arr) { struct find_test_setup_ctx *ctx = _ctx; ctx->rtctx->proj_name = project_name; obj setups; obj_array_index(wk, arr, 1, &setups); if (!setups) { return ir_cont; } obj_array_foreach(wk, setups, ctx, find_test_setup_iter); if (ctx->found) { return ir_done; } ++ctx->rtctx->proj_i; return ir_cont; } static bool load_test_setup(struct workspace *wk, struct run_test_ctx *rtctx, obj tests_dict) { bool res = false; struct find_test_setup_ctx ctx = { .rtctx = rtctx, }; obj_dict_foreach(wk, tests_dict, &ctx, find_test_setup_project_iter); if (!ctx.found) { if (rtctx->opts->setup) { LOG_E("invalid test setup: '%s'", rtctx->opts->setup); goto ret; } } res = true; ret: rtctx->proj_i = 0; return res; } /* * Test runner */ static void calculate_test_duration(struct test_result *res) { struct timespec end; if (clock_gettime(CLOCK_MONOTONIC, &end)) { LOG_E("error getting test end time: %s", strerror(errno)); return; } double secs = (double)end.tv_sec - (double)res->start.tv_sec; double ns = ((secs * 1000000000.0) + end.tv_nsec) - res->start.tv_nsec; res->dur = ns / 1000000000.0; } static void test_delay(void) { struct timespec req = { .tv_nsec = 10000000, }; nanosleep(&req, NULL); } static bool check_test_result_tap(struct workspace *wk, struct run_test_ctx *ctx, struct test_result *res) { struct tap_parse_result tap_result = { 0 }; tap_parse(res->cmd_ctx.out.buf, res->cmd_ctx.out.len, &tap_result); res->subtests.have = true; res->subtests.pass = tap_result.pass + tap_result.skip; res->subtests.total = tap_result.total; return tap_result.all_ok && res->status == 0; } static bool check_test_result_exitcode(struct workspace *wk, struct run_test_ctx *ctx, struct test_result *res) { if (res->cmd_ctx.status == 0) { return true; } else if (res->cmd_ctx.status == 77) { ++ctx->stats.total_skipped; return true; } else if (res->cmd_ctx.status == 99) { return false; } else { return false; } } static void collect_tests(struct workspace *wk, struct run_test_ctx *ctx) { uint32_t i; for (i = 0; i < ctx->opts->jobs; ++i) { if (!ctx->jobs[i].busy) { continue; } struct test_result *res = &ctx->jobs[i]; calculate_test_duration(res); enum run_cmd_state state = run_cmd_collect(&res->cmd_ctx); if (state != run_cmd_running && res->status == test_result_status_timedout) { run_cmd_ctx_destroy(&res->cmd_ctx); print_test_progress(wk, ctx, res, true); darr_push(&ctx->test_results, res); goto free_slot; } switch (state) { case run_cmd_running: { if (res->timeout > 0.0f && res->dur >= res->timeout) { bool force_kill = res->status == test_result_status_timedout && (res->dur - res->timeout) > 0.5f; run_cmd_kill(&res->cmd_ctx, force_kill); if (!res->status) { res->status = test_result_status_timedout; } } continue; } case run_cmd_error: res->status = test_result_status_failed; print_test_progress(wk, ctx, res, true); darr_push(&ctx->test_results, res); break; case run_cmd_finished: { bool ok; switch (res->test->protocol) { case test_protocol_tap: ok = check_test_result_tap(wk, ctx, res); break; default: ok = check_test_result_exitcode(wk, ctx, res); break; } if (!ok && res->test->should_fail) { ok = true; } if (ok) { if (res->test->should_fail) { ++ctx->stats.total_expect_fail_count; } res->status = test_result_status_ok; run_cmd_ctx_destroy(&res->cmd_ctx); } else { res->status = test_result_status_failed; } print_test_progress(wk, ctx, res, true); darr_push(&ctx->test_results, res); break; } } free_slot: res->busy = false; --ctx->busy_jobs; if (!res->test->is_parallel) { ctx->serial = false; } if (ctx->opts->fail_fast && ctx->stats.total_error_count) { break; } } } static void push_test(struct workspace *wk, struct run_test_ctx *ctx, struct obj_test *test, const char *argstr, uint32_t argc, const char *envstr, uint32_t envc) { uint32_t i; while (true) { if (ctx->serial && ctx->busy_jobs) { goto cont; } if (test->is_parallel) { for (i = 0; i < ctx->opts->jobs; ++i) { if (!ctx->jobs[i].busy) { goto found_slot; } } } else { if (!ctx->busy_jobs) { ctx->serial = true; i = 0; goto found_slot; } } cont: test_delay(); collect_tests(wk, ctx); } found_slot: ++ctx->busy_jobs; struct test_result *res = &ctx->jobs[i]; struct run_cmd_ctx *cmd_ctx = &res->cmd_ctx; *res = (struct test_result) { .busy = true, .test = test, .timeout = (test->timeout ? get_obj_number(wk, test->timeout) : 30.0f) * ctx->setup.timeout_multiplier, .cmd_ctx = { .flags = run_cmd_ctx_flag_async, }, }; if (ctx->opts->verbosity > 1) { cmd_ctx->flags |= run_cmd_ctx_flag_dont_capture; } if (test->workdir) { cmd_ctx->chdir = get_cstr(wk, test->workdir); } if (clock_gettime(CLOCK_MONOTONIC, &res->start)) { LOG_E("error getting test start time: %s", strerror(errno)); } print_test_progress(wk, ctx, res, ctx->serial); if (!run_cmd(cmd_ctx, argstr, argc, envstr, envc)) { res->busy = false; --ctx->busy_jobs; calculate_test_duration(res); res->status = test_result_status_failed; print_test_progress(wk, ctx, res, true); darr_push(&ctx->test_results, res); } } static enum iteration_result run_test(struct workspace *wk, void *_ctx, obj t) { struct run_test_ctx *ctx = _ctx; if (ctx->opts->fail_fast && ctx->stats.total_error_count) { return ir_done; } struct obj_test *test = get_obj_test(wk, t); obj cmdline; make_obj(wk, &cmdline, obj_array); if (ctx->setup.wrapper) { obj_array_extend(wk, cmdline, ctx->setup.wrapper); } obj_array_push(wk, cmdline, test->exe); if (test->args) { obj_array_extend_nodup(wk, cmdline, test->args); } const char *argstr, *envstr; uint32_t argc, envc; obj env; if (!environment_to_dict(wk, test->env, &env)) { UNREACHABLE; } if (ctx->setup.env) { obj setup_env; if (!environment_to_dict(wk, ctx->setup.env, &setup_env)) { UNREACHABLE; } obj merged; obj_dict_merge(wk, env, setup_env, &merged); env = merged; } join_args_argstr(wk, &argstr, &argc, cmdline); env_to_envstr(wk, &envstr, &envc, env); push_test(wk, ctx, test, argstr, argc, envstr, envc); return ir_cont; } /* * Test filtering and dispatch */ static enum iteration_result gather_project_tests_iter(struct workspace *wk, void *_ctx, obj val) { struct run_test_ctx *ctx = _ctx; struct obj_test *t = get_obj_test(wk, val); if (!(t->category == ctx->opts->cat && test_in_suite(wk, t->suites, ctx))) { return ir_cont; } obj_array_push(wk, ctx->collected_tests, val); ++ctx->stats.test_len; if (t->depends) { obj_array_extend_nodup(wk, ctx->deps, t->depends); } return ir_cont; } static int32_t test_compare(struct workspace *wk, void *_ctx, obj t1_id, obj t2_id) { struct obj_test *t1 = get_obj_test(wk, t1_id), *t2 = get_obj_test(wk, t2_id); int64_t p1 = t1->priority ? get_obj_number(wk, t1->priority) : 0, p2 = t2->priority ? get_obj_number(wk, t2->priority) : 0; if (p1 > p2) { return -1; } else if (p1 < p2) { return 1; } else if (t1->is_parallel && t2->is_parallel) { return 0; } else if (t1->is_parallel) { return 1; } else { return -1; } } static enum iteration_result list_tests_iter(struct workspace *wk, void *_ctx, obj test) { struct run_test_ctx *ctx = _ctx; struct obj_test *t = get_obj_test(wk, test); obj_printf(wk, "%#o", ctx->proj_name); if (t->suites) { obj_printf(wk, ":%o", t->suites); } obj_printf(wk, " - %#o\n", t->name); return ir_cont; } static enum iteration_result run_project_tests(struct workspace *wk, void *_ctx, obj proj_name, obj arr) { obj unfiltered_tests, tests; obj_array_index(wk, arr, 0, &unfiltered_tests); struct run_test_ctx *ctx = _ctx; make_obj(wk, &ctx->deps, obj_array); ctx->stats.test_i = 0; ctx->stats.error_count = 0; ctx->stats.test_len = 0; make_obj(wk, &ctx->collected_tests, obj_array); obj_array_foreach(wk, unfiltered_tests, ctx, gather_project_tests_iter); obj_array_sort(wk, NULL, ctx->collected_tests, test_compare, &tests); if (ctx->opts->list) { obj_array_foreach(wk, tests, ctx, list_tests_iter); return ir_cont; } else if (!ctx->stats.test_len) { return ir_cont; } if (get_obj_array(wk, ctx->deps)->len && !ctx->opts->no_rebuild) { obj ninja_cmd; obj_array_dedup(wk, ctx->deps, &ninja_cmd); if (ninja_run(wk, ninja_cmd, NULL, NULL) != 0) { LOG_W("failed to run ninja"); } } LOG_I("running %ss for project '%s'", test_category_label(ctx->opts->cat), get_cstr(wk, proj_name)); ctx->stats.ran_tests = true; ctx->proj_name = proj_name; if (!obj_array_foreach(wk, tests, ctx, run_test)) { return ir_err; } if (ctx->opts->fail_fast && ctx->stats.total_error_count) { return ir_done; } while (ctx->busy_jobs) { test_delay(); collect_tests(wk, ctx); } log_plain("\n"); ++ctx->proj_i; return ir_cont; } bool tests_run(struct test_options *opts, const char *argv0) { bool ret = false; SBUF_manual(tests_src); struct workspace wk; workspace_init_bare(&wk); wk.argv0 = argv0; if (!opts->jobs) { opts->jobs = 4; } struct run_test_ctx ctx = { .opts = opts, .setup = { .timeout_multiplier = 1.0f, }, }; darr_init(&ctx.test_results, 32, sizeof(struct test_result)); ctx.jobs = z_calloc(ctx.opts->jobs, sizeof(struct test_result)); { // load global opts obj option_info; if (!serial_load_from_private_dir(&wk, &option_info, output_path.option_info)) { goto ret; } obj_array_index(&wk, option_info, 0, &wk.global_opts); } { obj ninja_cmd; make_obj(&wk, &ninja_cmd, obj_array); obj_array_push(&wk, ninja_cmd, make_str(&wk, "build.ninja")); ninja_run(&wk, ninja_cmd, NULL, NULL); } { int fd; if (!fs_fileno(log_file(), &fd)) { return false; } if (opts->display == test_display_auto) { opts->display = test_display_dots; if (fs_is_a_tty_from_fd(fd)) { opts->display = test_display_bar; } } if (opts->display == test_display_bar) { uint32_t h; ctx.stats.term = true; term_winsize(fd, &h, &ctx.stats.term_width); } else if (opts->display == test_display_dots) { ctx.stats.term = false; } else { assert(false && "unreachable"); } } obj tests_dict; if (!serial_load_from_private_dir(&wk, &tests_dict, output_path.tests)) { goto ret; } if (!load_test_setup(&wk, &ctx, tests_dict)) { goto ret; } if (!obj_dict_foreach(&wk, tests_dict, &ctx, run_project_tests)) { goto ret; } if (opts->list) { ret = true; goto ret; } if (!ctx.stats.ran_tests) { LOG_I("no %ss defined", test_category_label(opts->cat)); } else { LOG_I("finished %d %ss, %d expected fail, %d fail, %d skipped", ctx.stats.total_count, test_category_label(opts->cat), ctx.stats.total_expect_fail_count, ctx.stats.total_error_count, ctx.stats.total_skipped ); } ret = true; uint32_t i; for (i = 0; i < ctx.test_results.len; ++i) { struct test_result *res = darr_get(&ctx.test_results, i); if (opts->print_summary || (res->status == test_result_status_failed || res->status == test_result_status_timedout)) { print_test_result(&wk, res); if (res->status == test_result_status_failed && res->cmd_ctx.err_msg) { log_plain(": %s", res->cmd_ctx.err_msg); } log_plain("\n"); } if (res->status == test_result_status_failed) { if (res->test->should_fail) { ret = false; } else { ret = false; if (res->cmd_ctx.out.len) { log_plain("stdout: '%s'\n", res->cmd_ctx.out.buf); } if (res->cmd_ctx.err.len) { log_plain("stderr: '%s'\n", res->cmd_ctx.err.buf); } } run_cmd_ctx_destroy(&res->cmd_ctx); } else if (res->status == test_result_status_timedout) { ret = false; } } ret: workspace_destroy_bare(&wk); darr_destroy(&ctx.test_results); z_free(ctx.jobs); return ret; } muon-0.2.0/src/coerce.c000066400000000000000000000326471441402162300146730ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "coerce.h" #include "functions/environment.h" #include "lang/interpreter.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" bool coerce_environment_from_kwarg(struct workspace *wk, struct args_kw *kw, bool set_subdir, obj *res) { if (kw->set) { if (get_obj_type(wk, kw->val) == obj_environment) { *res = kw->val; } else if (!coerce_key_value_dict(wk, kw->node, kw->val, res)) { return false; } } else { make_obj(wk, res, obj_dict); } set_default_environment_vars(wk, *res, set_subdir); return true; } struct coerce_environment_ctx { uint32_t err_node; obj res; }; static enum iteration_result coerce_environment_iter(struct workspace *wk, void *_ctx, obj val) { struct coerce_environment_ctx *ctx = _ctx; if (!typecheck(wk, ctx->err_node, val, obj_string)) { return false; } const struct str *ss = get_str(wk, val); if (str_has_null(ss)) { interp_error(wk, ctx->err_node, "environment string %o must not contain NUL", val); return ir_err; } const char *eql; if (!(eql = strchr(ss->s, '='))) { interp_error(wk, ctx->err_node, "invalid env element %o; env elements must be of the format key=value", val); return ir_err; } uint32_t key_len = eql - ss->s; obj key = make_strn(wk, ss->s, key_len); val = make_strn(wk, ss->s + key_len + 1, ss->len - (key_len + 1)); obj_dict_set(wk, ctx->res, key, val); return ir_cont; } static enum iteration_result typecheck_environment_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { uint32_t err_node = *(uint32_t *)_ctx; const struct str *k = get_str(wk, key), *v = get_str(wk, val); if (!k->len) { interp_error(wk, err_node, "environment key may not be an empty string (value is '%s')", v->s); return ir_err; } else if (str_has_null(k)) { interp_error(wk, err_node, "environment key may not contain NUL"); return ir_err; } else if (str_has_null(v)) { interp_error(wk, err_node, "environment value may not contain NUL"); return ir_err; } else if (strchr(k->s, '=')) { interp_error(wk, err_node, "environment key '%s' contains '='", k->s); return ir_err; } return ir_cont; } bool coerce_key_value_dict(struct workspace *wk, uint32_t err_node, obj val, obj *res) { make_obj(wk, res, obj_dict); struct coerce_environment_ctx ctx = { .err_node = err_node, .res = *res, }; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: return coerce_environment_iter(wk, &ctx, val) != ir_err; case obj_array: return obj_array_foreach_flat(wk, val, &ctx, coerce_environment_iter); case obj_dict: if (!typecheck_dict(wk, err_node, val, obj_string)) { return false; } *res = val; break; default: interp_error(wk, err_node, "unable to coerce type '%s' into key=value dict", obj_type_to_s(t)); return false; } if (!obj_dict_foreach(wk, *res, &err_node, typecheck_environment_dict_iter)) { return false; } return true; } bool coerce_include_type(struct workspace *wk, const struct str *str, uint32_t err_node, enum include_type *res) { static const char *include_type_strs[] = { [include_type_preserve] = "preserve", [include_type_system] = "system", [include_type_non_system] = "non-system", 0, }; uint32_t i; for (i = 0; include_type_strs[i]; ++i) { if (str_eql(str, &WKSTR(include_type_strs[i]))) { *res = i; return true; } } interp_error(wk, err_node, "invalid value for include_type: %s", str->s); return false; } bool coerce_num_to_string(struct workspace *wk, uint32_t node, obj val, obj *res) { switch (get_obj_type(wk, val)) { case obj_number: { *res = make_strf(wk, "%" PRId64, get_obj_number(wk, val)); break; } case obj_string: { *res = val; break; } default: interp_error(wk, node, "unable to coerce %o to string", val); return false; } return true; } bool coerce_string(struct workspace *wk, uint32_t node, obj val, obj *res) { switch (get_obj_type(wk, val)) { case obj_bool: if (get_obj_bool(wk, val)) { *res = make_str(wk, "true"); } else { *res = make_str(wk, "false"); } break; case obj_file: *res = *get_obj_file(wk, val); break; case obj_number: { *res = make_strf(wk, "%" PRId64, get_obj_number(wk, val)); break; } case obj_string: { *res = val; break; } default: interp_error(wk, node, "unable to coerce %o to string", val); return false; } return true; } struct coerce_string_array_ctx { uint32_t node; obj arr; }; static enum iteration_result coerce_string_array_iter(struct workspace *wk, void *_ctx, obj val) { struct coerce_string_array_ctx *ctx = _ctx; obj res; if (!coerce_string(wk, ctx->node, val, &res)) { return ir_err; } obj_array_push(wk, ctx->arr, res); return ir_cont; } bool coerce_string_array(struct workspace *wk, uint32_t node, obj arr, obj *res) { make_obj(wk, res, obj_array); struct coerce_string_array_ctx ctx = { .node = node, .arr = *res, }; return obj_array_foreach(wk, arr, &ctx, coerce_string_array_iter); } bool coerce_executable(struct workspace *wk, uint32_t node, obj val, obj *res) { obj str; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_file: str = *get_obj_file(wk, val); break; case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *o = get_obj_build_target(wk, val); SBUF(dest); SBUF(rel); path_join(wk, &dest, get_cstr(wk, o->build_dir), get_cstr(wk, o->build_name)); path_relative_to(wk, &rel, wk->build_root, dest.buf); path_executable(wk, &dest, rel.buf); str = sbuf_into_str(wk, &dest); break; } case obj_external_program: { struct obj_external_program *o = get_obj_external_program(wk, val); if (!o->found) { interp_error(wk, node, "a not found external_program cannot be used here"); return ir_err; } str = o->full_path; break; } default: interp_error(wk, node, "unable to coerce '%s' into executable", obj_type_to_s(t)); return false; } *res = str; return true; } bool coerce_requirement(struct workspace *wk, struct args_kw *kw_required, enum requirement_type *requirement) { if (kw_required->set) { enum obj_type t = get_obj_type(wk, kw_required->val); if (t == obj_bool) { if (get_obj_bool(wk, kw_required->val)) { *requirement = requirement_required; } else { *requirement = requirement_auto; } } else if (t == obj_feature_opt) { switch (get_obj_feature_opt(wk, kw_required->val)) { case feature_opt_disabled: *requirement = requirement_skip; break; case feature_opt_enabled: *requirement = requirement_required; break; case feature_opt_auto: *requirement = requirement_auto; break; } } else { interp_error(wk, kw_required->node, "expected type %s or %s, got %s", obj_type_to_s(obj_bool), obj_type_to_s(obj_feature_opt), obj_type_to_s(t) ); return false; } } else { *requirement = requirement_required; } return true; } typedef bool (*exists_func)(const char *); enum coerce_into_files_mode { mode_input, mode_output, }; struct coerce_into_files_ctx { uint32_t node; obj arr; const char *type, *output_dir; exists_func exists; enum coerce_into_files_mode mode; }; static enum iteration_result coerce_custom_target_output_iter(struct workspace *wk, void *_ctx, obj val) { struct coerce_into_files_ctx *ctx = _ctx; obj_array_push(wk, ctx->arr, val); return ir_cont; } bool coerce_string_to_file(struct workspace *wk, const char *dir, obj string, obj *res) { const char *p = get_cstr(wk, string); SBUF(path); if (path_is_absolute(p)) { const struct str *ss = get_str(wk, string); path_copy(wk, &path, ss->s); } else { path_join(wk, &path, dir, p); } _path_normalize(wk, &path, true); make_obj(wk, res, obj_file); *get_obj_file(wk, *res) = sbuf_into_str(wk, &path); return true; } static bool coerce_into_file(struct workspace *wk, struct coerce_into_files_ctx *ctx, obj val, obj *file) { enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: { SBUF(buf); switch (ctx->mode) { case mode_input: if (!coerce_string_to_file(wk, get_cstr(wk, current_project(wk)->cwd), val, file)) { return ir_err; } if (!ctx->exists(get_file_path(wk, *file))) { interp_error(wk, ctx->node, "%s %o does not exist", ctx->type, val); return ir_err; } break; case mode_output: if (!path_is_basename(get_cstr(wk, val))) { interp_error(wk, ctx->node, "output file '%s' contains path separators", get_cstr(wk, val)); return ir_err; } path_join(wk, &buf, ctx->output_dir, get_cstr(wk, val)); make_obj(wk, file, obj_file); *get_obj_file(wk, *file) = sbuf_into_str(wk, &buf); break; default: assert(false); return ir_err; } break; } case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { if (ctx->mode == mode_output) { goto type_error; } struct obj_build_target *tgt = get_obj_build_target(wk, val); SBUF(path); path_join(wk, &path, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); make_obj(wk, file, obj_file); *get_obj_file(wk, *file) = sbuf_into_str(wk, &path); break; } case obj_file: if (ctx->mode == mode_output) { goto type_error; } *file = val; break; default: type_error: interp_error(wk, ctx->node, "unable to coerce object with type %s into %s", obj_type_to_s(t), ctx->type); return false; } return true; } static enum iteration_result coerce_into_files_iter(struct workspace *wk, void *_ctx, obj val) { struct coerce_into_files_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_custom_target: { if (ctx->mode == mode_output) { goto type_error; } if (!obj_array_foreach(wk, get_obj_custom_target(wk, val)->output, ctx, coerce_custom_target_output_iter)) { return ir_err; } break; } case obj_string: case obj_file: case obj_both_libs: case obj_build_target: { obj file; if (!coerce_into_file(wk, ctx, val, &file)) { return ir_err; } obj_array_push(wk, ctx->arr, file); break; } default: type_error: interp_error(wk, ctx->node, "unable to coerce object with type %s into %s", obj_type_to_s(t), ctx->type); return ir_err; } return ir_cont; } static bool _coerce_files(struct workspace *wk, uint32_t node, obj val, obj *res, const char *type_name, exists_func exists, enum coerce_into_files_mode mode, const char *output_dir) { make_obj(wk, res, obj_array); struct coerce_into_files_ctx ctx = { .node = node, .arr = *res, .type = type_name, .exists = exists, .mode = mode, .output_dir = output_dir, }; switch (get_obj_type(wk, val)) { case obj_array: return obj_array_foreach_flat(wk, val, &ctx, coerce_into_files_iter); default: switch (coerce_into_files_iter(wk, &ctx, val)) { case ir_err: return false; default: return true; } } } bool coerce_output_files(struct workspace *wk, uint32_t node, obj val, const char *output_dir, obj *res) { return _coerce_files(wk, node, val, res, "output file", NULL, mode_output, output_dir); } bool coerce_files(struct workspace *wk, uint32_t node, obj val, obj *res) { return _coerce_files(wk, node, val, res, "file", fs_file_exists, mode_input, 0); } bool coerce_file(struct workspace *wk, uint32_t node, obj val, obj *res) { struct coerce_into_files_ctx ctx = { .node = node, .arr = *res, .type = "file", .exists = fs_file_exists, .mode = mode_input, }; return coerce_into_file(wk, &ctx, val, res); } bool coerce_dirs(struct workspace *wk, uint32_t node, obj val, obj *res) { return _coerce_files(wk, node, val, res, "directory", fs_dir_exists, mode_input, 0); } struct include_directories_iter_ctx { uint32_t node; obj res; bool is_system; }; static enum iteration_result include_directories_iter(struct workspace *wk, void *_ctx, obj v) { struct include_directories_iter_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, v); if (t == obj_include_directory) { obj_array_push(wk, ctx->res, v); return ir_cont; } else if (t != obj_string) { interp_error(wk, ctx->node, "unable to coerce %o to include_directory", v); return ir_err; } obj path = v; SBUF(buf1); SBUF(buf2); const char *p = get_cstr(wk, path); if (!path_is_absolute(p)) { SBUF(abs); path_join(wk, &abs, get_cstr(wk, current_project(wk)->cwd), p); path = sbuf_into_str(wk, &abs); } p = get_cstr(wk, path); if (!fs_dir_exists(p)) { interp_error(wk, ctx->node, "directory '%s' does not exist", get_cstr(wk, path)); return ir_err; } obj inc; struct obj_include_directory *d; if (path_is_subpath(wk->source_root, p)) { path_relative_to(wk, &buf1, wk->source_root, p); path_join(wk, &buf2, wk->build_root, buf1.buf); make_obj(wk, &inc, obj_include_directory); d = get_obj_include_directory(wk, inc); d->path = sbuf_into_str(wk, &buf2); d->is_system = ctx->is_system; obj_array_push(wk, ctx->res, inc); } make_obj(wk, &inc, obj_include_directory); d = get_obj_include_directory(wk, inc); d->path = path; d->is_system = ctx->is_system; obj_array_push(wk, ctx->res, inc); return ir_cont; } bool coerce_include_dirs(struct workspace *wk, uint32_t node, obj val, bool is_system, obj *res) { struct include_directories_iter_ctx ctx = { .node = node, .is_system = is_system, }; make_obj(wk, &ctx.res, obj_array); if (!obj_array_foreach_flat(wk, val, &ctx, include_directories_iter)) { return false; } *res = ctx.res; return true; } muon-0.2.0/src/compilers.c000066400000000000000000000555561441402162300154340ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Owen Rafferty * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "compilers.h" #include "error.h" #include "functions/machine.h" #include "guess.h" #include "lang/workspace.h" #include "log.h" #include "options.h" #include "platform/path.h" #include "platform/run_cmd.h" const char * compiler_type_to_s(enum compiler_type t) { switch (t) { case compiler_posix: return "posix"; case compiler_gcc: return "gcc"; case compiler_clang: return "clang"; case compiler_apple_clang: return "clang"; case compiler_clang_llvm_ir: return "clang"; case compiler_nasm: return "nasm"; case compiler_yasm: return "yasm"; case compiler_type_count: UNREACHABLE; } UNREACHABLE_RETURN; } const char * linker_type_to_s(enum linker_type t) { switch (t) { case linker_posix: return "ld"; case linker_gcc: return "ld.bfd"; case linker_apple: return "ld64"; case linker_type_count: UNREACHABLE; } UNREACHABLE_RETURN; } static const char *compiler_language_names[compiler_language_count] = { [compiler_language_null] = "null", [compiler_language_c] = "c", [compiler_language_c_hdr] = "c_hdr", [compiler_language_cpp] = "cpp", [compiler_language_cpp_hdr] = "cpp_hdr", [compiler_language_c_obj] = "c_obj", [compiler_language_objc] = "objc", [compiler_language_assembly] = "assembly", [compiler_language_llvm_ir] = "llvm_ir", [compiler_language_nasm] = "nasm", }; const char * compiler_language_to_s(enum compiler_language l) { assert(l < compiler_language_count); return compiler_language_names[l]; } bool s_to_compiler_language(const char *s, enum compiler_language *l) { uint32_t i; for (i = 0; i < compiler_language_count; ++i) { if (strcmp(s, compiler_language_names[i]) == 0) { *l = i; return true; } } return false; } static const char *compiler_language_exts[compiler_language_count][10] = { [compiler_language_c] = { "c" }, [compiler_language_c_hdr] = { "h" }, [compiler_language_cpp] = { "cc", "cpp", "cxx", "C" }, [compiler_language_cpp_hdr] = { "hh", "hpp", "hxx" }, [compiler_language_c_obj] = { "o", "obj" }, [compiler_language_objc] = { "m", "mm", "M" }, [compiler_language_assembly] = { "S" }, [compiler_language_llvm_ir] = { "ll" }, [compiler_language_nasm] = { "asm" }, }; bool filename_to_compiler_language(const char *str, enum compiler_language *l) { uint32_t i, j; const char *ext; if (!(ext = strrchr(str, '.'))) { return false; } ++ext; for (i = 0; i < compiler_language_count; ++i) { for (j = 0; compiler_language_exts[i][j]; ++j) { if (strcmp(ext, compiler_language_exts[i][j]) == 0) { *l = i; return true; } } } return false; } const char * compiler_language_extension(enum compiler_language l) { return compiler_language_exts[l][0]; } enum compiler_language coalesce_link_languages(enum compiler_language cur, enum compiler_language new) { switch (new) { case compiler_language_null: case compiler_language_c_hdr: case compiler_language_cpp_hdr: case compiler_language_llvm_ir: break; case compiler_language_assembly: if (!cur) { return compiler_language_assembly; } break; case compiler_language_nasm: case compiler_language_c: case compiler_language_c_obj: case compiler_language_objc: if (!cur) { return compiler_language_c; } break; case compiler_language_cpp: if (!cur || cur == compiler_language_c || cur == compiler_language_assembly) { return compiler_language_cpp; } break; case compiler_language_count: UNREACHABLE; } return cur; } static bool run_cmd_arr(struct workspace *wk, struct run_cmd_ctx *cmd_ctx, obj cmd_arr, const char *arg) { obj args; obj_array_dup(wk, cmd_arr, &args); obj_array_push(wk, args, make_str(wk, arg)); const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, args); if (!run_cmd(cmd_ctx, argstr, argc, NULL, 0)) { run_cmd_ctx_destroy(cmd_ctx); return false; } return true; } static bool compiler_detect_c_or_cpp(struct workspace *wk, obj cmd_arr, obj *comp_id) { // helpful: mesonbuild/compilers/detect.py:350 struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, "--version")) { run_cmd_ctx_destroy(&cmd_ctx); return false; } if (cmd_ctx.status != 0) { cmd_ctx = (struct run_cmd_ctx) { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, "-v")) { run_cmd_ctx_destroy(&cmd_ctx); return false; } } enum compiler_type type; bool unknown = true; obj ver; if (cmd_ctx.status != 0) { goto detection_over; } if (strstr(cmd_ctx.out.buf, "Apple") && strstr(cmd_ctx.out.buf, "clang")) { type = compiler_apple_clang; } else if (strstr(cmd_ctx.out.buf, "clang") || strstr(cmd_ctx.out.buf, "Clang")) { type = compiler_clang; } else if (strstr(cmd_ctx.out.buf, "Free Software Foundation")) { type = compiler_gcc; } else { goto detection_over; } if (!guess_version(wk, cmd_ctx.out.buf, &ver)) { ver = make_str(wk, "unknown"); } unknown = false; LLOG_I("detected compiler %s ", compiler_type_to_s(type)); obj_fprintf(wk, log_file(), "%o (%o), ", ver, cmd_arr); log_plain("linker %s\n", linker_type_to_s(compilers[type].linker)); detection_over: if (unknown) { LOG_W("unable to detect compiler type, falling back on posix compiler"); type = compiler_posix; ver = make_str(wk, "unknown"); } make_obj(wk, comp_id, obj_compiler); struct obj_compiler *comp = get_obj_compiler(wk, *comp_id); comp->cmd_arr = cmd_arr; comp->type = type; comp->ver = ver; run_cmd_ctx_destroy(&cmd_ctx); return true; } static bool compiler_detect_nasm(struct workspace *wk, obj cmd_arr, obj *comp_id) { struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, cmd_arr, "--version")) { run_cmd_ctx_destroy(&cmd_ctx); return false; } enum compiler_type type; obj ver; if (strstr(cmd_ctx.out.buf, "NASM")) { type = compiler_nasm; } else if (strstr(cmd_ctx.out.buf, "yasm")) { type = compiler_yasm; } else { // Just assume it is nasm type = compiler_nasm; } if (!guess_version(wk, cmd_ctx.out.buf, &ver)) { ver = make_str(wk, "unknown"); } obj new_cmd; obj_array_dup(wk, cmd_arr, &new_cmd); { uint32_t addr_bits = machine_cpu_address_bits(); enum machine_system sys = machine_system(); const char *plat; SBUF(define); if (sys == machine_system_windows || sys == machine_system_cygwin) { plat = "win"; sbuf_pushf(wk, &define, "WIN%d", addr_bits); } else if (sys == machine_system_darwin) { plat = "macho"; sbuf_pushs(wk, &define, "MACHO"); } else { plat = "elf"; sbuf_pushs(wk, &define, "ELF"); } obj_array_push(wk, new_cmd, make_strf(wk, "-f%s%d", plat, addr_bits)); obj_array_push(wk, new_cmd, make_strf(wk, "-D%s", define.buf)); if (addr_bits == 64) { obj_array_push(wk, new_cmd, make_str(wk, "-D__x86_64__")); } } make_obj(wk, comp_id, obj_compiler); struct obj_compiler *comp = get_obj_compiler(wk, *comp_id); comp->cmd_arr = new_cmd; comp->type = type; comp->ver = ver; comp->lang = compiler_language_nasm; run_cmd_ctx_destroy(&cmd_ctx); return true; } static bool compiler_get_libdirs(struct workspace *wk, struct obj_compiler *comp) { struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_arr(wk, &cmd_ctx, comp->cmd_arr, "--print-search-dirs") || cmd_ctx.status) { goto done; } const char *key = "libraries: "; char *s, *e; bool beginning_of_line = true; for (s = cmd_ctx.out.buf; *s; ++s) { if (beginning_of_line && strncmp(s, key, strlen(key)) == 0) { s += strlen(key); if (*s == '=') { ++s; } e = strchr(s, '\n'); struct str str = { .s = s, .len = e ? (uint32_t)(e - s) : strlen(s), }; comp->libdirs = str_split(wk, &str, &WKSTR(ENV_PATH_SEP_STR)); goto done; } beginning_of_line = *s == '\n'; } done: run_cmd_ctx_destroy(&cmd_ctx); if (!comp->libdirs) { const char *libdirs[] = { "/usr/lib", "/usr/local/lib", "/lib", NULL }; make_obj(wk, &comp->libdirs, obj_array); uint32_t i; for (i = 0; libdirs[i]; ++i) { obj_array_push(wk, comp->libdirs, make_str(wk, libdirs[i])); } } return true; } bool compiler_detect(struct workspace *wk, obj *comp, enum compiler_language lang) { static const char *compiler_option[compiler_language_count] = { [compiler_language_c] = "env.CC", [compiler_language_cpp] = "env.CXX", [compiler_language_objc] = "env.OBJC", [compiler_language_nasm] = "env.NASM", }; if (!compiler_option[lang]) { return false; } obj cmd_arr; get_option_value(wk, NULL, compiler_option[lang], &cmd_arr); switch (lang) { case compiler_language_c: case compiler_language_cpp: case compiler_language_objc: if (!compiler_detect_c_or_cpp(wk, cmd_arr, comp)) { return false; } struct obj_compiler *compiler = get_obj_compiler(wk, *comp); compiler_get_libdirs(wk, compiler); compiler->lang = lang; return true; case compiler_language_nasm: if (!compiler_detect_nasm(wk, cmd_arr, comp)) { return false; } return true; default: LOG_E("tried to get a compiler for unsupported language '%s'", compiler_language_to_s(lang)); return false; } } #define COMPILER_ARGS(...) \ static const char *argv[] = __VA_ARGS__; \ static struct args args = { \ .args = argv, \ .len = ARRAY_LEN(argv) \ }; /* posix compilers */ static const struct args * compiler_posix_args_compile_only(void) { COMPILER_ARGS({ "-c" }); return &args; } static const struct args * compiler_posix_args_preprocess_only(void) { COMPILER_ARGS({ "-E" }); return &args; } static const struct args * compiler_posix_args_output(const char *f) { COMPILER_ARGS({ "-o", NULL }); argv[1] = f; return &args; } static const struct args * compiler_posix_args_optimization(uint32_t lvl) { COMPILER_ARGS({ NULL }); switch ((enum compiler_optimization_lvl)lvl) { case compiler_optimization_lvl_0: argv[0] = "-O0"; break; case compiler_optimization_lvl_1: case compiler_optimization_lvl_2: case compiler_optimization_lvl_3: argv[0] = "-O1"; break; case compiler_optimization_lvl_none: case compiler_optimization_lvl_g: case compiler_optimization_lvl_s: args.len = 0; break; } return &args; } static const struct args * compiler_posix_args_debug(void) { COMPILER_ARGS({ "-g" }); return &args; } static const struct args * compiler_posix_args_include(const char *dir) { COMPILER_ARGS({ "-I", NULL }); argv[1] = dir; return &args; } static const struct args * compiler_posix_args_define(const char *define) { COMPILER_ARGS({ "-D", NULL }); argv[1] = define; return &args; } /* gcc compilers */ static const struct args * compiler_gcc_args_preprocess_only(void) { COMPILER_ARGS({ "-E", "-P" }); return &args; } static const struct args * compiler_gcc_args_include_system(const char *dir) { COMPILER_ARGS({ "-isystem", NULL }); argv[1] = dir; return &args; } static const struct args * compiler_gcc_args_deps(const char *out_target, const char *out_file) { COMPILER_ARGS({ "-MD", "-MQ", NULL, "-MF", NULL }); argv[2] = out_target; argv[4] = out_file; return &args; } static const struct args * compiler_gcc_args_optimization(uint32_t lvl) { COMPILER_ARGS({ NULL }); switch ((enum compiler_optimization_lvl)lvl) { case compiler_optimization_lvl_none: args.len = 0; break; case compiler_optimization_lvl_0: argv[0] = "-O0"; break; case compiler_optimization_lvl_1: argv[0] = "-O1"; break; case compiler_optimization_lvl_2: argv[0] = "-O2"; break; case compiler_optimization_lvl_3: argv[0] = "-O3"; break; case compiler_optimization_lvl_g: argv[0] = "-Og"; break; case compiler_optimization_lvl_s: argv[0] = "-Os"; break; } return &args; } static const struct args * compiler_gcc_args_warning_lvl(uint32_t lvl) { COMPILER_ARGS({ NULL, NULL, NULL }); args.len = 0; switch ((enum compiler_warning_lvl)lvl) { case compiler_warning_lvl_3: argv[args.len] = "-Wpedantic"; ++args.len; /* fallthrough */ case compiler_warning_lvl_2: argv[args.len] = "-Wextra"; ++args.len; /* fallthrough */ case compiler_warning_lvl_1: argv[args.len] = "-Wall"; ++args.len; /* fallthrough */ case compiler_warning_lvl_0: break; } return &args; } static const struct args * compiler_gcc_args_werror(void) { COMPILER_ARGS({ "-Werror" }); return &args; } static const struct args * compiler_gcc_args_set_std(const char *std) { static char buf[BUF_SIZE_S]; COMPILER_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-std=%s", std); return &args; } static const struct args * compiler_gcc_args_pgo(uint32_t stage) { COMPILER_ARGS({ NULL, NULL }); args.len = 1; switch ((enum compiler_pgo_stage)stage) { case compiler_pgo_generate: argv[0] = "-fprofile-generate"; break; case compiler_pgo_use: argv[1] = "-fprofile-correction"; ++args.len; argv[0] = "-fprofile-use"; break; } return &args; } static const struct args * compiler_gcc_args_pic(void) { COMPILER_ARGS({ "-fpic" }); return &args; } static const struct args * compiler_gcc_args_pie(void) { COMPILER_ARGS({ "-fpie" }); return &args; } static const struct args * compiler_gcc_args_sanitize(const char *sanitizers) { static char buf[BUF_SIZE_S]; COMPILER_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-fsanitize=%s", sanitizers); return &args; } static const struct args * compiler_gcc_args_visibility(uint32_t type) { COMPILER_ARGS({ NULL, NULL }); args.len = 1; switch ((enum compiler_visibility_type)type) { case compiler_visibility_default: argv[0] = "-fvisibility=default"; break; case compiler_visibility_internal: argv[0] = "-fvisibility=internal"; break; case compiler_visibility_protected: argv[0] = "-fvisibility=protected"; break; case compiler_visibility_inlineshidden: argv[1] = "-fvisibility-inlines-hidden"; ++args.len; // fallthrough case compiler_visibility_hidden: argv[0] = "-fvisibility=hidden"; break; default: assert(false && "unreachable"); } return &args; } static const struct args * compiler_gcc_args_specify_lang(const char *language) { COMPILER_ARGS({ "-x", NULL, }); argv[1] = language; return &args; } static const struct args * compiler_gcc_args_color_output(const char *when) { static char buf[BUF_SIZE_S]; COMPILER_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-fdiagnostics-color=%s", when); return &args; } static const struct args * compiler_gcc_args_lto(void) { COMPILER_ARGS({ "-flto" }); return &args; } static const struct args * compiler_arg_empty_0(void) { COMPILER_ARGS({ NULL }); args.len = 0; return &args; } static const struct args * compiler_arg_empty_1i(uint32_t _) { COMPILER_ARGS({ NULL }); args.len = 0; return &args; } static const struct args * compiler_arg_empty_1s(const char * _) { COMPILER_ARGS({ NULL }); args.len = 0; return &args; } static const struct args * compiler_arg_empty_2s(const char *_, const char *__) { COMPILER_ARGS({ NULL }); args.len = 0; return &args; } struct compiler compilers[compiler_type_count]; struct linker linkers[linker_type_count]; const struct language languages[compiler_language_count] = { [compiler_language_null] = { 0 }, [compiler_language_c] = { .is_header = false }, [compiler_language_c_hdr] = { .is_header = true }, [compiler_language_cpp] = { .is_header = false }, [compiler_language_cpp_hdr] = { .is_header = true }, [compiler_language_c_obj] = { .is_linkable = true }, [compiler_language_assembly] = { 0 }, [compiler_language_llvm_ir] = { 0 }, }; static void build_compilers(void) { struct compiler empty = { .args = { .deps = compiler_arg_empty_2s, .compile_only = compiler_arg_empty_0, .preprocess_only = compiler_arg_empty_0, .output = compiler_arg_empty_1s, .optimization = compiler_arg_empty_1i, .debug = compiler_arg_empty_0, .warning_lvl = compiler_arg_empty_1i, .werror = compiler_arg_empty_0, .set_std = compiler_arg_empty_1s, .include = compiler_arg_empty_1s, .include_system = compiler_arg_empty_1s, .pgo = compiler_arg_empty_1i, .pic = compiler_arg_empty_0, .pie = compiler_arg_empty_0, .sanitize = compiler_arg_empty_1s, .define = compiler_arg_empty_1s, .visibility = compiler_arg_empty_1i, .specify_lang = compiler_arg_empty_1s, .color_output = compiler_arg_empty_1s, .enable_lto = compiler_arg_empty_0, } }; struct compiler clang_llvm_ir = empty; clang_llvm_ir.args.compile_only = compiler_posix_args_compile_only; clang_llvm_ir.args.output = compiler_posix_args_output; struct compiler posix = empty; posix.args.compile_only = compiler_posix_args_compile_only; posix.args.preprocess_only = compiler_posix_args_preprocess_only; posix.args.output = compiler_posix_args_output; posix.args.optimization = compiler_posix_args_optimization; posix.args.debug = compiler_posix_args_debug; posix.args.include = compiler_posix_args_include; posix.args.include_system = compiler_posix_args_include; posix.args.define = compiler_posix_args_define; posix.linker = linker_posix; struct compiler gcc = posix; gcc.args.preprocess_only = compiler_gcc_args_preprocess_only; gcc.args.deps = compiler_gcc_args_deps; gcc.args.optimization = compiler_gcc_args_optimization; gcc.args.warning_lvl = compiler_gcc_args_warning_lvl; gcc.args.werror = compiler_gcc_args_werror; gcc.args.set_std = compiler_gcc_args_set_std; gcc.args.include_system = compiler_gcc_args_include_system; gcc.args.pgo = compiler_gcc_args_pgo; gcc.args.pic = compiler_gcc_args_pic; gcc.args.pie = compiler_gcc_args_pie; gcc.args.sanitize = compiler_gcc_args_sanitize; gcc.args.visibility = compiler_gcc_args_visibility; gcc.args.specify_lang = compiler_gcc_args_specify_lang; gcc.args.color_output = compiler_gcc_args_color_output; gcc.args.enable_lto = compiler_gcc_args_lto; gcc.deps = compiler_deps_gcc; gcc.linker = linker_gcc; struct compiler apple_clang = gcc; apple_clang.linker = linker_apple; compilers[compiler_posix] = posix; compilers[compiler_gcc] = gcc; compilers[compiler_clang] = gcc; compilers[compiler_apple_clang] = apple_clang; compilers[compiler_clang_llvm_ir] = clang_llvm_ir; struct compiler nasm = empty; nasm.args.output = compiler_posix_args_output; nasm.args.optimization = compiler_posix_args_optimization; nasm.args.debug = compiler_posix_args_debug; nasm.args.include = compiler_posix_args_include; nasm.args.include_system = compiler_posix_args_include; nasm.args.define = compiler_posix_args_define; nasm.linker = linker_posix; compilers[compiler_nasm] = nasm; compilers[compiler_yasm] = nasm; } static const struct args * linker_posix_args_lib(const char *s) { COMPILER_ARGS({ "-l", NULL }); argv[1] = s; return &args; } /* technically not a posix linker argument, but include it here since it is so * common */ static const struct args * linker_posix_args_shared(void) { COMPILER_ARGS({ "-shared" }); return &args; } static const struct args * linker_gcc_args_as_needed(void) { COMPILER_ARGS({ "-Wl,--as-needed" }); return &args; } static const struct args * linker_gcc_args_no_undefined(void) { COMPILER_ARGS({ "-Wl,--no-undefined" }); return &args; } static const struct args * linker_gcc_args_start_group(void) { COMPILER_ARGS({ "-Wl,--start-group" }); return &args; } static const struct args * linker_gcc_args_end_group(void) { COMPILER_ARGS({ "-Wl,--end-group" }); return &args; } static const struct args * linker_gcc_args_soname(const char *soname) { static char buf[BUF_SIZE_S]; COMPILER_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-Wl,-soname,%s", soname); return &args; } static const struct args * linker_gcc_args_rpath(const char *rpath) { static char buf[BUF_SIZE_S]; COMPILER_ARGS({ buf }); snprintf(buf, BUF_SIZE_S, "-Wl,-rpath,%s", rpath); return &args; } static const struct args * linker_gcc_args_allow_shlib_undefined(void) { COMPILER_ARGS({ "-Wl,--allow-shlib-undefined" }); return &args; } static const struct args * linker_gcc_args_export_dynamic(void) { COMPILER_ARGS({ "-Wl,-export-dynamic" }); return &args; } static const struct args * linker_gcc_args_fatal_warnings(void) { COMPILER_ARGS({ "-Wl,--fatal-warnings" }); return &args; } static const struct args * linker_gcc_args_whole_archive(void) { COMPILER_ARGS({ "-Wl,--whole-archive" }); return &args; } static const struct args * linker_gcc_args_no_whole_archive(void) { COMPILER_ARGS({ "-Wl,--no-whole-archive" }); return &args; } static void build_linkers(void) { /* linkers */ struct linker empty = { .args = { .lib = compiler_arg_empty_1s, .as_needed = compiler_arg_empty_0, .no_undefined = compiler_arg_empty_0, .start_group = compiler_arg_empty_0, .end_group = compiler_arg_empty_0, .shared = compiler_arg_empty_0, .soname = compiler_arg_empty_1s, .rpath = compiler_arg_empty_1s, .pgo = compiler_arg_empty_1i, .sanitize = compiler_arg_empty_1s, .allow_shlib_undefined = compiler_arg_empty_0, .export_dynamic = compiler_arg_empty_0, .fatal_warnings = compiler_arg_empty_0, .whole_archive = compiler_arg_empty_0, .no_whole_archive = compiler_arg_empty_0, } }; struct linker posix = empty; posix.args.lib = linker_posix_args_lib; posix.args.shared = linker_posix_args_shared; struct linker gcc = posix; gcc.args.as_needed = linker_gcc_args_as_needed; gcc.args.no_undefined = linker_gcc_args_no_undefined; gcc.args.start_group = linker_gcc_args_start_group; gcc.args.end_group = linker_gcc_args_end_group; gcc.args.soname = linker_gcc_args_soname; gcc.args.rpath = linker_gcc_args_rpath; gcc.args.pgo = compiler_gcc_args_pgo; gcc.args.sanitize = compiler_gcc_args_sanitize; gcc.args.allow_shlib_undefined = linker_gcc_args_allow_shlib_undefined; gcc.args.export_dynamic = linker_gcc_args_export_dynamic; gcc.args.fatal_warnings = linker_gcc_args_fatal_warnings; gcc.args.whole_archive = linker_gcc_args_whole_archive; gcc.args.no_whole_archive = linker_gcc_args_no_whole_archive; gcc.args.enable_lto = compiler_gcc_args_lto; struct linker apple = posix; linkers[linker_posix] = posix; linkers[linker_gcc] = gcc; linkers[linker_apple] = apple; } void compilers_init(void) { build_compilers(); build_linkers(); } enum ar_type { ar_posix, ar_gcc, }; static enum ar_type compiler_detect_ar_type(void) { struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd_argv(&cmd_ctx, (char *[]){ "ar", "--version", NULL }, NULL, 0)) { run_cmd_ctx_destroy(&cmd_ctx); return ar_posix; } enum ar_type ret = ar_posix; if (cmd_ctx.status == 0 && strstr(cmd_ctx.out.buf, "Free Software Foundation")) { ret = ar_gcc; } run_cmd_ctx_destroy(&cmd_ctx); return ret; } const char * ar_arguments(void) { static enum ar_type ar_type; static bool ar_type_initialized = false; if (!ar_type_initialized) { ar_type = compiler_detect_ar_type(); ar_type_initialized = true; } switch (ar_type) { case ar_gcc: return "csrD"; case ar_posix: return "csr"; default: UNREACHABLE_RETURN; } } muon-0.2.0/src/data/000077500000000000000000000000001441402162300141645ustar00rootroot00000000000000muon-0.2.0/src/data/bucket_array.c000066400000000000000000000071041441402162300170050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "data/bucket_array.h" #include "log.h" #include "platform/mem.h" void init_bucket(struct bucket_array *ba, struct bucket *b) { b->mem = z_calloc(ba->item_size, ba->bucket_size); } uint64_t bucket_array_size(struct bucket_array *ba) { return ba->buckets.len * ba->item_size * ba->bucket_size; } void bucket_array_init(struct bucket_array *ba, uint32_t bucket_size, uint32_t item_size) { assert(item_size > 0); *ba = (struct bucket_array) { .item_size = item_size, .bucket_size = bucket_size, }; darr_init(&ba->buckets, 1, sizeof(struct bucket)); darr_push(&ba->buckets, &(struct bucket) { 0 }); init_bucket(ba, darr_get(&ba->buckets, 0)); } void bucket_array_clear(struct bucket_array *ba) { uint32_t i; struct bucket *b; for (i = 0; i < ba->buckets.len; ++i) { b = darr_get(&ba->buckets, i); b->len = 0; } ba->buckets.len = 1; ba->len = 0; } void bucket_array_save(const struct bucket_array *ba, struct bucket_array_save *save) { struct bucket *b; b = darr_get(&ba->buckets, ba->tail_bucket); save->tail_bucket = ba->tail_bucket; save->tail_bucket_len = b->len; } void bucket_array_restore(struct bucket_array *ba, const struct bucket_array_save *save) { struct bucket *b; b = darr_get(&ba->buckets, save->tail_bucket); assert(save->tail_bucket_len <= b->len); ba->len -= b->len - save->tail_bucket_len; b->len = save->tail_bucket_len; memset(&b->mem[b->len * ba->item_size], 0, (ba->bucket_size - b->len) * ba->item_size); uint32_t bi; for (bi = save->tail_bucket + 1; bi < ba->buckets.len; ++bi) { b = darr_get(&ba->buckets, bi); memset(b->mem, 0, b->len * ba->item_size); ba->len -= b->len; b->len = 0; } ba->tail_bucket = save->tail_bucket; } void * bucket_array_pushn(struct bucket_array *ba, const void *data, uint32_t data_len, uint32_t reserve) { void *dest; struct bucket *b; assert(reserve >= data_len); assert(reserve < ba->bucket_size); b = darr_get(&ba->buckets, ba->tail_bucket); if (b->len + reserve > ba->bucket_size) { if (ba->tail_bucket >= ba->buckets.len - 1) { darr_push(&ba->buckets, &(struct bucket) { 0 }); ++ba->tail_bucket; b = darr_get(&ba->buckets, ba->tail_bucket); init_bucket(ba, b); } else { ++ba->tail_bucket; b = darr_get(&ba->buckets, ba->tail_bucket); assert(b->mem); assert(b->len == 0); } } dest = b->mem + (b->len * ba->item_size); if (data) { memcpy(dest, data, ba->item_size * data_len); } b->len += reserve; ba->len += reserve; return dest; } void * bucket_array_push(struct bucket_array *ba, const void *item) { return bucket_array_pushn(ba, item, 1, 1); } void * bucket_array_get(const struct bucket_array *ba, uint32_t i) { struct bucket *b; uint32_t bucket_i = i % ba->bucket_size; b = darr_get(&ba->buckets, i / ba->bucket_size); assert(bucket_i < b->len); return b->mem + (bucket_i * ba->item_size); } void bucket_array_destroy(struct bucket_array *ba) { uint32_t i; struct bucket *b; for (i = 0; i < ba->buckets.len; ++i) { b = darr_get(&ba->buckets, i); z_free(b->mem); } darr_destroy(&ba->buckets); } bool bucket_array_lookup_pointer(struct bucket_array *ba, const uint8_t *p, uint64_t *ret) { uint32_t i; for (i = 0; i < ba->buckets.len; ++i) { struct bucket *b = darr_get(&ba->buckets, i); if (b->mem <= p && p < b->mem + (b->len * ba->item_size)) { *ret = i * ba->bucket_size + (p - b->mem) / ba->item_size; return true; } } return false; } muon-0.2.0/src/data/darr.c000066400000000000000000000045201441402162300152610ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include "data/darr.h" #include "log.h" #include "platform/mem.h" void darr_init(struct darr *da, size_t initial, size_t item_size) { assert(item_size > 0); *da = (struct darr) { .item_size = item_size, .cap = initial, .e = z_malloc(initial * item_size), }; } void darr_destroy(struct darr *da) { if (da->e) { z_free(da->e); da->e = NULL; } } void darr_clear(struct darr *da) { da->len = 0; } static uint8_t * darr_point_at(const struct darr *da, size_t i) { return da->e + (i * da->item_size); } static void * darr_get_mem(struct darr *da) { size_t i, newcap; ++da->len; /* ensure_mem_size(elem, size, ++(*len), cap); */ if (da->len > da->cap) { assert(da->cap); newcap = da->cap * 2; if (newcap < da->len) { newcap = da->len * 2; } da->cap = newcap; da->e = z_realloc(da->e, da->cap * da->item_size); } else { /* NOTE: uncomment the below line to cause a realloc for * _every_ push into a darr. This can help find bugs where you * held a pointer into a darr too long. */ /* da->e = z_realloc(da->e, da->cap * da->item_size); */ } i = da->len - 1; return darr_point_at(da, i); } void darr_grow_by(struct darr *da, size_t size) { da->len += size - 1; darr_get_mem(da); } size_t darr_push(struct darr *da, const void *item) { memcpy(darr_get_mem(da), item, da->item_size); return da->len - 1; } void * darr_get(const struct darr *da, size_t i) { if (i >= da->len) { L("index %" PRIu64 " out of bounds (%" PRIu64 ")", (uint64_t)i, (uint64_t)da->len); } assert(i < da->len); return darr_point_at(da, i); } void darr_del(struct darr *da, size_t i) { assert(i < da->len); da->len--; if (da->len > 0 && da->len != i) { memmove(darr_point_at(da, i), darr_point_at(da, da->len), da->item_size); } } static struct { void *user_ctx; sort_func func; } darr_sort_ctx; static int32_t darr_sort_compare(const void *a, const void *b) { return darr_sort_ctx.func(a, b, darr_sort_ctx.user_ctx); } void darr_sort(struct darr *da, void *ctx, sort_func func) { darr_sort_ctx.user_ctx = ctx; darr_sort_ctx.func = func; qsort(da->e, da->len, da->item_size, darr_sort_compare); } muon-0.2.0/src/data/hash.c000066400000000000000000000135161441402162300152610ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "data/darr.h" #include "data/hash.h" #include "log.h" #include "platform/mem.h" #define k_empty 0x80 // 0b10000000 #define k_deleted 0xfe // 0b11111110 #define k_full(v) !(v & (1 << 7)) // k_full = 0b0xxxxxxx #define ASSERT_VALID_CAP(cap) assert(cap >= 8); assert((cap & (cap - 1)) == 0); #define LOAD_FACTOR 0.5f static uint64_t fnv_1a_64_str(const struct hash *hash, const void *_key) { const char *key = *(const char **)_key; uint64_t h = 14695981039346656037u; uint16_t i; for (i = 0; key[i]; i++) { h ^= key[i]; h *= 1099511628211u; } return h; } static uint64_t fnv_1a_64(const struct hash *hash, const void *_key) { const uint8_t *key = _key; uint64_t h = 14695981039346656037u; uint16_t i; for (i = 0; i < hash->keys.item_size; i++) { h ^= key[i]; h *= 1099511628211u; } return h; } struct hash_elem { uint64_t val, keyi; }; static void fill_meta_with_empty(struct hash *h) { const uint32_t len = h->cap >> 3; uint64_t *e = (uint64_t *)h->meta.e; uint32_t i; for (i = 0; i < len; ++i) { e[i] = 9259542123273814144u; /* this number is just k_empty (128) 8 times: * ((128 << 56) | (128 << 48) | (128 << 40) | (128 << 32) * | (128 << 24) | (128 << 16) | (128 << 8) | (128)) */ } } static void prepare_table(struct hash *h) { fill_meta_with_empty(h); } static bool hash_keycmp_memcmp(const struct hash *h, const void *a, const void *b) { return memcmp(a, b, h->keys.item_size) == 0; } void hash_init(struct hash *h, size_t cap, uint32_t keysize) { ASSERT_VALID_CAP(cap); *h = (struct hash) { .cap = cap, .capm = cap - 1, .max_load = (size_t)((float)cap * LOAD_FACTOR) }; darr_init(&h->meta, h->cap, sizeof(uint8_t)); darr_init(&h->e, h->cap, sizeof(struct hash_elem)); darr_init(&h->keys, h->cap, keysize); prepare_table(h); h->keycmp = hash_keycmp_memcmp; h->hash_func = fnv_1a_64; } static bool hash_keycmp_strcmp(const struct hash *_h, const void *a, const void *b) { return strcmp(*(char **)a, *(char **)b) == 0; } void hash_init_str(struct hash *h, size_t cap) { hash_init(h, cap, sizeof(char *)); h->keycmp = hash_keycmp_strcmp; h->hash_func = fnv_1a_64_str; } void hash_destroy(struct hash *h) { darr_destroy(&h->meta); darr_destroy(&h->e); darr_destroy(&h->keys); } void hash_for_each(struct hash *h, void *ctx, iterator_func ifnc) { size_t i; for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } switch (ifnc(ctx, &((struct hash_elem *)h->e.e)[i].val)) { case ir_cont: break; case ir_done: case ir_err: return; } } } void hash_for_each_with_keys(struct hash *h, void *ctx, hash_with_keys_iterator_func ifnc) { size_t i; struct hash_elem *he; for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } he = &((struct hash_elem *)h->e.e)[i]; switch (ifnc(ctx, h->keys.e + he->keyi * h->keys.item_size, he->val)) { case ir_cont: break; case ir_done: case ir_err: return; } } } void hash_clear(struct hash *h) { h->len = h->load = 0; fill_meta_with_empty(h); } static void probe(const struct hash *h, const void *key, struct hash_elem **ret_he, uint8_t **ret_meta, uint64_t *hv) { #define match ((meta & 0x7f) == h2 \ && h->keycmp(h, h->keys.e + (h->keys.item_size * he->keyi), key)) struct hash_elem *he; *hv = h->hash_func(h, key); const uint64_t h1 = *hv >> 7, h2 = *hv & 0x7f; uint8_t meta; uint64_t hvi = h1 & h->capm; meta = ((uint8_t *)h->meta.e)[hvi]; he = &((struct hash_elem *)h->e.e)[hvi]; while (meta == k_deleted || (k_full(meta) && !match)) { hvi = (hvi + 1) & h->capm; meta = ((uint8_t *)h->meta.e)[hvi]; he = &((struct hash_elem *)h->e.e)[hvi]; } *ret_meta = &((uint8_t *)h->meta.e)[hvi]; *ret_he = he; #undef match } static void resize(struct hash *h, size_t newcap) { ASSERT_VALID_CAP(newcap); assert(h->len <= newcap); uint32_t i; struct hash_elem *ohe, *he; uint64_t hv; uint8_t *meta; void *key; struct hash newh = (struct hash) { .cap = newcap, .capm = newcap - 1, .keys = h->keys, .len = h->len, .load = h->load, .max_load = (size_t)((float)newcap * LOAD_FACTOR), .hash_func = h->hash_func, .keycmp = h->keycmp, }; darr_init(&newh.meta, newh.cap, sizeof(uint8_t)); darr_init(&newh.e, newh.cap, sizeof(struct hash_elem)); prepare_table(&newh); for (i = 0; i < h->cap; ++i) { if (!k_full(((uint8_t *)h->meta.e)[i])) { continue; } ohe = &((struct hash_elem *)h->e.e)[i]; key = h->keys.e + (h->keys.item_size * ohe->keyi); probe(&newh, key, &he, &meta, &hv); assert(!k_full(*meta)); *he = *ohe; *meta = hv & 0x7f; } darr_destroy(&h->meta); darr_destroy(&h->e); *h = newh; } uint64_t * hash_get(const struct hash *h, const void *key) { struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); return k_full(*meta) ? &he->val : NULL; } uint64_t * hash_get_str(const struct hash *h, const char *key) { return hash_get(h, &key); } void hash_unset(struct hash *h, const void *key) { struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); if (k_full(*meta)) { *meta = k_deleted; --h->len; } assert(hash_get(h, key) == NULL); } void hash_unset_str(struct hash *h, const char *key) { hash_unset(h, &key); } void hash_set(struct hash *h, const void *key, uint64_t val) { if (h->load > h->max_load) { resize(h, h->cap << 1); } struct hash_elem *he; uint64_t hv; uint8_t *meta; probe(h, key, &he, &meta, &hv); if (k_full(*meta)) { he->val = val; } else { he->keyi = darr_push(&h->keys, key); he->val = val; *meta = hv & 0x7f; ++h->len; ++h->load; } } void hash_set_str(struct hash *h, const char *key, uint64_t val) { hash_set(h, &key, val); } muon-0.2.0/src/embedded.c000066400000000000000000000011151441402162300151460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "embedded.h" struct embedded_file { const char *name, *src; }; #ifdef MUON_BOOTSTRAPPED #include "embedded_files.h" #else static struct embedded_file embedded[] = { 0 }; static uint32_t embedded_len = 0; #endif const char * embedded_get(const char *name) { uint32_t i; for (i = 0; i < embedded_len; ++i) { if (strcmp(embedded[i].name, name) == 0) { return embedded[i].src; } } return NULL; } muon-0.2.0/src/error.c000066400000000000000000000174221441402162300145560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include // exit #include #include "buf_size.h" #include "data/darr.h" #include "error.h" #include "log.h" #include "platform/mem.h" struct error_diagnostic_message { uint32_t line, col; enum log_level lvl; const char *msg; uint32_t src_idx; }; struct error_diagnostic_source { struct source src; uint64_t id; }; static struct { struct darr messages; struct darr sources; bool init; } error_diagnostic_store; void error_diagnostic_store_init(void) { darr_init(&error_diagnostic_store.messages, 32, sizeof(struct error_diagnostic_message)); darr_init(&error_diagnostic_store.sources, 4, sizeof(struct error_diagnostic_source)); error_diagnostic_store.init = true; } uint32_t error_diagnostic_store_push_src(struct source *src) { uint32_t i; struct error_diagnostic_source *s = NULL; for (i = 0; i < error_diagnostic_store.sources.len; ++i) { s = darr_get(&error_diagnostic_store.sources, i); // TODO: this is not super robust, as it relies on chance that // two sources don't get allocated at the same place if (s->id == (uint64_t)src && strcmp(s->src.label, src->label) == 0) { break; } else { s = NULL; } } if (!s) { struct source dup; fs_source_dup(src, &dup); darr_push(&error_diagnostic_store.sources, &(struct error_diagnostic_source) { .src = dup, .id = (uint64_t)src, }); i = error_diagnostic_store.sources.len - 1; } s = darr_get(&error_diagnostic_store.sources, i); return i; } void error_diagnostic_store_push(uint32_t src_idx, uint32_t line, uint32_t col, enum log_level lvl, const char *msg) { uint32_t mlen = strlen(msg); char *m = z_calloc(mlen + 1, 1); memcpy(m, msg, mlen); darr_push(&error_diagnostic_store.messages, &(struct error_diagnostic_message){ .line = line, .col = col, .lvl = lvl, .msg = m, .src_idx = src_idx, }); } static int32_t error_diagnostic_store_compare_except_lvl(const void *_a, const void *_b, void *ctx) { const struct error_diagnostic_message *a = _a, *b = _b; int32_t v; if (a->src_idx != b->src_idx) { return (int32_t)a->src_idx - (int32_t)b->src_idx; } else if (a->line != b->line) { return (int32_t)a->line - (int32_t)b->line; } else if (a->col != b->col) { return (int32_t)a->col - (int32_t)b->col; } else if ((v = strcmp(a->msg, b->msg)) != 0) { return v; } else { return 0; } } static int32_t error_diagnostic_store_compare(const void *_a, const void *_b, void *ctx) { const struct error_diagnostic_message *a = _a, *b = _b; int32_t v; if ((v = error_diagnostic_store_compare_except_lvl(a, b, ctx)) != 0) { return v; } else if (a->lvl != b->lvl) { return a->lvl > b->lvl ? 1 : -1; } else { return 0; } } void error_diagnostic_store_replay(enum error_diagnostic_store_replay_opts opts, bool *saw_error) { error_diagnostic_store.init = false; uint32_t i; struct error_diagnostic_message *msg; struct error_diagnostic_source *last_src = NULL, *cur_src; darr_sort(&error_diagnostic_store.messages, NULL, error_diagnostic_store_compare); size_t tail, initial_len = error_diagnostic_store.messages.len; if (error_diagnostic_store.messages.len > 1) { struct error_diagnostic_message *prev_msg, tmp; tail = error_diagnostic_store.messages.len; uint32_t initial_len = error_diagnostic_store.messages.len; msg = darr_get(&error_diagnostic_store.messages, 0); darr_push(&error_diagnostic_store.messages, msg); for (i = 1; i < initial_len; ++i) { prev_msg = darr_get(&error_diagnostic_store.messages, i - 1); msg = darr_get(&error_diagnostic_store.messages, i); if (error_diagnostic_store_compare_except_lvl(prev_msg, msg, NULL) == 0) { continue; } tmp = *msg; darr_push(&error_diagnostic_store.messages, &tmp); } } else { tail = 0; } *saw_error = false; struct source src = { 0 }; for (i = tail; i < error_diagnostic_store.messages.len; ++i) { msg = darr_get(&error_diagnostic_store.messages, i); if (opts & error_diagnostic_store_replay_werror) { msg->lvl = log_error; } if ((opts & error_diagnostic_store_replay_errors_only) && msg->lvl != log_error) { continue; } if (msg->lvl == log_error) { *saw_error = true; } if ((cur_src = darr_get(&error_diagnostic_store.sources, msg->src_idx)) != last_src) { if (opts & error_diagnostic_store_replay_include_sources) { if (last_src) { log_plain("\n"); } log_plain("%s%s%s\n", log_clr() ? "\033[31;1m" : "", cur_src->src.label, log_clr() ? "\033[0m" : ""); } last_src = cur_src; src = cur_src->src; if (!(opts & error_diagnostic_store_replay_include_sources)) { src.len = 0; } } error_message(&src, msg->line, msg->col, msg->lvl, msg->msg); } for (i = 0; i < initial_len; ++i) { msg = darr_get(&error_diagnostic_store.messages, i); z_free((char *)msg->msg); } for (i = 0; i < error_diagnostic_store.sources.len; ++i) { cur_src = darr_get(&error_diagnostic_store.sources, i); fs_source_destroy(&cur_src->src); } darr_destroy(&error_diagnostic_store.messages); darr_destroy(&error_diagnostic_store.sources); } void error_unrecoverable(const char *fmt, ...) { va_list ap; if (log_clr()) { log_plain("\033[31m"); } log_plain("fatal error"); if (log_clr()) { log_plain("\033[0m"); } log_plain(": "); va_start(ap, fmt); log_plainv(fmt, ap); log_plain("\n"); va_end(ap); exit(1); } static bool list_line_internal(struct source *src, uint32_t lno, uint32_t *start_of_line, uint32_t *line_pre_len) { *start_of_line = 0; uint64_t i, cl = 1; for (i = 0; i < src->len; ++i) { if (src->src[i] == '\n') { ++cl; *start_of_line = i + 1; } if (cl == lno) { break; } } if (i == src->len) { return false; } char line_pre[32] = { 0 }; *line_pre_len = snprintf(line_pre, 31, "%3d | ", lno); log_plain("%s", line_pre); for (i = *start_of_line; src->src[i] && src->src[i] != '\n'; ++i) { if (src->src[i] == '\t') { log_plain(" "); } else { putc(src->src[i], stderr); } } log_plain("\n"); return true; } void list_line_range(struct source *src, uint32_t lno, uint32_t list_amt) { uint32_t _, __; uint32_t lstart = 0, lend, i; if (lno > list_amt / 2) { lstart = lno - list_amt / 2; } lend = lstart + list_amt; log_plain("-> %s%s%s\n", log_clr() ? "\033[32m" : "", src->label, log_clr() ? "\033[0m" : "" ); for (i = lstart; i < lend; ++i) { list_line_internal(src, i, &_, &__); } } void error_message(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *msg) { if (error_diagnostic_store.init) { error_diagnostic_store_push(error_diagnostic_store_push_src(src), line, col, lvl, msg); return; } log_plain("%s:%d:%d: ", src->label, line, col); if (log_clr()) { log_plain("\033[%sm%s\033[0m ", log_level_clr[lvl], log_level_name[lvl]); } else { log_plain("%s ", log_level_name[lvl]); } log_plain("%s\n", msg); if (!src->len) { return; } uint32_t line_pre_len, i, sol; if (!list_line_internal(src, line, &sol, &line_pre_len)) { return; } for (i = 0; i < line_pre_len; ++i) { log_plain(" "); } if (sol + col >= src->len) { log_plain("^\n"); return; } for (i = 0; i < col; ++i) { if (src->src[sol + i] == '\t') { log_plain(" "); } else { log_plain(i == col - 1 ? "^" : " "); } } log_plain("\n"); } void error_messagev(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *fmt, va_list args) { static char buf[BUF_SIZE_4k]; vsnprintf(buf, BUF_SIZE_4k, fmt, args); error_message(src, line, col, lvl, buf); } void error_messagef(struct source *src, uint32_t line, uint32_t col, enum log_level lvl, const char *fmt, ...) { va_list ap; va_start(ap, fmt); error_messagev(src, line, col, lvl, fmt, ap); va_end(ap); } muon-0.2.0/src/external/000077500000000000000000000000001441402162300150755ustar00rootroot00000000000000muon-0.2.0/src/external/bestline.c000066400000000000000000000010111441402162300170370ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "external/bestline.h" #include "platform/mem.h" char * muon_bestline(const char *prompt) { return bestlineWithHistory(prompt, 0); } void muon_bestline_free(const char *line) { z_free((void *)line); } int muon_bestline_history_add(const char *line) { return bestlineHistoryAdd(line); } void muon_bestline_history_free(void) { bestlineHistoryFree(); } muon-0.2.0/src/external/bestline_null.c000066400000000000000000000007521441402162300201040ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "external/bestline.h" #include "log.h" char * muon_bestline(const char *prompt) { static char buf[2048]; fputs(prompt, log_file()); fgets(buf, 2048, stdin); return buf; } void muon_bestline_free(const char *line) { } int muon_bestline_history_add(const char *line) { return 0; } void muon_bestline_history_free(void) { } muon-0.2.0/src/external/libarchive.c000066400000000000000000000052551441402162300173600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "external/libarchive.h" #include "lang/string.h" #include "log.h" #include "platform/path.h" const bool have_libarchive = true; static int copy_data(struct archive *ar, struct archive *aw) { int r; const void *buff; size_t size; la_int64_t offset; while (true) { if ((r = archive_read_data_block(ar, &buff, &size, &offset)) == ARCHIVE_EOF) { return ARCHIVE_OK; } else if (r < ARCHIVE_OK) { return r; } if ((r = archive_write_data_block(aw, buff, size, offset)) < ARCHIVE_OK) { LOG_E("error writing archive data block: %s\n", archive_error_string(aw)); return r; } } } bool muon_archive_extract(const char *buf, size_t size, const char *dest_path) { bool res = false; struct archive *a; struct archive *ext; struct archive_entry *entry; int flags; int r; /* Select which attributes we want to restore. */ flags = ARCHIVE_EXTRACT_TIME; flags |= ARCHIVE_EXTRACT_PERM; flags |= ARCHIVE_EXTRACT_ACL; flags |= ARCHIVE_EXTRACT_FFLAGS; a = archive_read_new(); archive_read_support_format_all(a); archive_read_support_filter_all(a); ext = archive_write_disk_new(); archive_write_disk_set_options(ext, flags); archive_write_disk_set_standard_lookup(ext); if ((r = archive_read_open_memory(a, buf, size))) { // may not work, a might not be initialized ?? LOG_E("error opening archive: %s\n", archive_error_string(a)); goto ret; } SBUF_manual(path); while (true) { if ((r = archive_read_next_header(a, &entry)) == ARCHIVE_EOF) { break; } else if (r < ARCHIVE_OK) { LOG_W("%s\n", archive_error_string(a)); } else if (r < ARCHIVE_WARN) { LOG_E("%s\n", archive_error_string(a)); goto ret; } path_join(NULL, &path, dest_path, archive_entry_pathname(entry)); archive_entry_copy_pathname(entry, path.buf); if ((r = archive_write_header(ext, entry)) < ARCHIVE_OK) { LOG_W("%s\n", archive_error_string(ext)); } else if (archive_entry_size(entry) > 0) { if ((r = copy_data(a, ext)) < ARCHIVE_OK) { LOG_W("%s\n", archive_error_string(ext)); } else if (r < ARCHIVE_WARN) { LOG_E("%s\n", archive_error_string(ext)); goto ret; } } if ((r = archive_write_finish_entry(ext)) < ARCHIVE_OK) { LOG_W("%s\n", archive_error_string(ext)); } else if (r < ARCHIVE_WARN) { LOG_E("%s\n", archive_error_string(ext)); goto ret; } } res = true; ret: sbuf_destroy(&path); if (a) { archive_read_close(a); archive_read_free(a); } if (ext) { archive_write_close(ext); archive_write_free(ext); } return res; } muon-0.2.0/src/external/libarchive_null.c000066400000000000000000000005371441402162300204100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "external/libarchive.h" #include "log.h" const bool have_libarchive = false; bool muon_archive_extract(const char *buf, size_t size, const char *dest_path) { LOG_W("libarchive not enabled"); return false; } muon-0.2.0/src/external/libcurl.c000066400000000000000000000055261441402162300167050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include "external/libcurl.h" #include "log.h" #include "platform/mem.h" const bool have_libcurl = true; static struct { bool init; } fetch_ctx = { 0 }; void muon_curl_init(void) { assert(!fetch_ctx.init); if (curl_global_init(CURL_GLOBAL_DEFAULT) == 0) { fetch_ctx.init = true; } } void muon_curl_deinit(void) { if (!fetch_ctx.init) { return; } curl_global_cleanup(); fetch_ctx.init = false; } struct write_data_ctx { uint8_t *buf; uint64_t len, cap; }; static size_t write_data(void *src, size_t size, size_t nmemb, void *_ctx) { struct write_data_ctx *ctx = _ctx; uint64_t want_to_write = size * nmemb; if (want_to_write + ctx->len > ctx->cap) { ctx->cap = want_to_write + ctx->len; ctx->buf = z_realloc(ctx->buf, ctx->cap); } memcpy(&ctx->buf[ctx->len], src, want_to_write); ctx->len += want_to_write; return nmemb; } bool muon_curl_fetch(const char *url, uint8_t **buf, uint64_t *len) { CURL *curl_handle; CURLcode err; char errbuf[CURL_ERROR_SIZE] = { 0 }; LOG_I("fetching '%s'", url); if (!fetch_ctx.init) { LOG_E("curl is not initialized"); goto err0; } if (!(curl_handle = curl_easy_init())) { LOG_E("failed to get curl handle"); goto err0; } if ((err = curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errbuf)) != CURLE_OK) { goto err1; } if ((err = curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1) != CURLE_OK)) { goto err1; } /* set URL to get here */ if ((err = curl_easy_setopt(curl_handle, CURLOPT_URL, url)) != CURLE_OK) { goto err1; } /* Switch on full protocol/debug output while testing */ if ((err = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L)) != CURLE_OK) { goto err1; } /* disable progress meter, set to 0L to enable it */ if ((err = curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L)) != CURLE_OK) { goto err1; } /* send all data to this function */ if ((err = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data)) != CURLE_OK) { goto err1; } struct write_data_ctx ctx = { 0 }; /* write the page body to this file handle */ if ((err = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &ctx)) != CURLE_OK) { goto err1; } /* get it! */ if ((err = curl_easy_perform(curl_handle)) != CURLE_OK) { goto err1; } *buf = ctx.buf; *len = ctx.len; /* cleanup curl stuff */ curl_easy_cleanup(curl_handle); return true; err1: if (*errbuf) { LOG_E("curl failed to fetch '%s': %s", url, errbuf); } else if (err != CURLE_OK) { LOG_E("curl failed to fetch '%s': %s", url, curl_easy_strerror(err)); } else { LOG_E("curl failed to fetch '%s'", url); } curl_easy_cleanup(curl_handle); err0: return false; } muon-0.2.0/src/external/libcurl_null.c000066400000000000000000000006131441402162300177270ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "external/libcurl.h" #include "log.h" const bool have_libcurl = false; void muon_curl_init(void) { } void muon_curl_deinit(void) { } bool muon_curl_fetch(const char *url, uint8_t **buf, uint64_t *len) { LOG_W("libcurl not enabled"); return false; } muon-0.2.0/src/external/libpkgconf.c000066400000000000000000000242361441402162300173660ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Ariadne Conill * SPDX-FileCopyrightText: Masayuki Yamamoto * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "buf_size.h" #include "external/libpkgconf.h" #include "lang/object.h" #include "lang/workspace.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" const bool have_libpkgconf = true; static struct { pkgconf_client_t client; pkgconf_cross_personality_t *personality; const int maxdepth; bool init; } pkgconf_ctx = { .maxdepth = 200, }; static bool #if defined(LIBPKGCONF_VERSION) && LIBPKGCONF_VERSION >= 10900 error_handler(const char *msg, const pkgconf_client_t *client, void *data) #else error_handler(const char *msg, const pkgconf_client_t * client, const void *data) #endif { if (log_should_print(log_debug)) { log_plain("dbg libpkgconf: %s", msg); } return true; } static bool muon_pkgconf_init(struct workspace *wk) { // HACK: TODO: libpkgconf breaks if you try use it after deiniting a // client. Also there are memory leaks abound. if (pkgconf_ctx.init) { return true; } pkgconf_ctx.personality = pkgconf_cross_personality_default(); pkgconf_client_init(&pkgconf_ctx.client, error_handler, NULL, pkgconf_ctx.personality); obj opt; get_option_value(wk, current_project(wk), "pkg_config_path", &opt); const struct str *pkg_config_path = get_str(wk, opt); #ifdef MUON_STATIC if (!pkg_config_path->len) { LOG_E("Unable to determine pkgconf search path. Please set " "PKG_CONFIG_PATH or -Dpkg_config_path to an appropriate value."); return false; } #endif if (pkg_config_path->len) { pkgconf_path_split(pkg_config_path->s, &pkgconf_ctx.client.dir_list, true); } else { // pkgconf_client_dir_list_build uses PKG_CONFIG_PATH and // PKG_CONFIG_LIBDIR from the environment, as well as the // builtin path (personality->dir_list). We currently // intercept PKG_CONFIG_PATH and turn it into an option, so the // above branch should always be taken if PKG_CONFIG_PATH is // set. pkgconf_client_dir_list_build(&pkgconf_ctx.client, pkgconf_ctx.personality); } pkgconf_ctx.init = true; return true; } #if 0 static void muon_pkgconf_deinit(void) { return; pkgconf_path_free(&pkgconf_ctx.personality->dir_list); pkgconf_path_free(&pkgconf_ctx.personality->filter_libdirs); pkgconf_path_free(&pkgconf_ctx.personality->filter_includedirs); pkgconf_client_deinit(&pkgconf_ctx.client); } #endif static const char * pkgconf_strerr(int err) { switch (err) { case PKGCONF_PKG_ERRF_OK: return "ok"; case PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND: return "not found"; case PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH: return "ver mismatch"; case PKGCONF_PKG_ERRF_PACKAGE_CONFLICT: return "package conflict"; case PKGCONF_PKG_ERRF_DEPGRAPH_BREAK: return "depgraph break"; } return "unknown"; } typedef unsigned int (*apply_func)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth); struct pkgconf_lookup_ctx { apply_func apply_func; struct workspace *wk; struct pkgconf_info *info; obj libdirs; obj name; bool is_static; }; struct find_lib_path_ctx { bool is_static; bool found; const char *name; struct sbuf *buf, *name_buf; }; static bool check_lib_path(struct workspace *wk, struct find_lib_path_ctx *ctx, const char *lib_path) { enum ext { ext_a, ext_so, ext_dll_a, ext_count }; static const char *ext[] = { [ext_a] = ".a", [ext_so] = ".so", [ext_dll_a] = ".dll.a" }; static const uint8_t ext_order_static[] = { ext_a, ext_so, ext_dll_a }, ext_order_dynamic[] = { ext_so, ext_dll_a, ext_a }, *ext_order; if (ctx->is_static) { ext_order = ext_order_static; } else { ext_order = ext_order_dynamic; } uint32_t i; /* * TODO * on Windows, ld is searching for several library names when direct * linking to a DLL, not necessarly 'lib***.dll.a". * Also, ld is searching for '*.dll' in $prefix/bin, not $prefix/lib * See https://sourceware.org/binutils/docs/ld/WIN32.html * section "direct linking to a dll" */ for (i = 0; i < ext_count; ++i) { sbuf_clear(ctx->name_buf); sbuf_pushf(wk, ctx->name_buf, "lib%s%s", ctx->name, ext[ext_order[i]]); path_join(wk, ctx->buf, lib_path, ctx->name_buf->buf); if (fs_file_exists(ctx->buf->buf)) { ctx->found = true; return true; } } return false; } static enum iteration_result find_lib_path_iter(struct workspace *wk, void *_ctx, obj val_id) { struct find_lib_path_ctx *ctx = _ctx; if (check_lib_path(wk, ctx, get_cstr(wk, val_id))) { return ir_done; } return ir_cont; } static obj find_lib_path(pkgconf_client_t *client, struct pkgconf_lookup_ctx *ctx, const char *name) { SBUF(buf); SBUF(name_buf); struct find_lib_path_ctx find_lib_path_ctx = { .buf = &buf, .name_buf = &name_buf, .name = name, .is_static = ctx->is_static }; if (!obj_array_foreach(ctx->wk, ctx->libdirs, &find_lib_path_ctx, find_lib_path_iter)) { return 0; } else if (!find_lib_path_ctx.found) { return 0; } return sbuf_into_str(ctx->wk, &buf); } static bool apply_and_collect(pkgconf_client_t *client, pkgconf_pkg_t *world, void *_ctx, int maxdepth) { struct pkgconf_lookup_ctx *ctx = _ctx; int err; pkgconf_node_t *node; pkgconf_list_t list = PKGCONF_LIST_INITIALIZER; obj str; bool ret = true; err = ctx->apply_func(client, world, &list, maxdepth); if (err != PKGCONF_PKG_ERRF_OK) { LOG_E("apply_func failed: %s", pkgconf_strerr(err)); ret = false; goto ret; } PKGCONF_FOREACH_LIST_ENTRY(list.head, node) { const pkgconf_fragment_t *frag = node->data; /* L("got option: -'%c' '%s'", frag->type, frag->data); */ switch (frag->type) { case 'I': if (!pkgconf_fragment_has_system_dir(client, frag)) { make_obj(ctx->wk, &str, obj_include_directory); struct obj_include_directory *o = get_obj_include_directory(ctx->wk, str); o->path = make_str(ctx->wk, frag->data); o->is_system = false; obj_array_push(ctx->wk, ctx->info->includes, str); } break; case 'L': str = make_str(ctx->wk, frag->data); obj_array_push(ctx->wk, ctx->libdirs, str); break; case 'l': { obj path; if ((path = find_lib_path(client, ctx, frag->data))) { if (!obj_array_in(ctx->wk, ctx->info->libs, str)) { L("library '%s' found for dependency '%s'", get_cstr(ctx->wk, path), get_cstr(ctx->wk, ctx->name)); obj_array_push(ctx->wk, ctx->info->libs, path); } } else { LOG_W("library '%s' not found for dependency '%s'", frag->data, get_cstr(ctx->wk, ctx->name)); obj_array_push(ctx->wk, ctx->info->not_found_libs, make_str(ctx->wk, frag->data)); } break; } default: if (frag->type) { obj_array_push(ctx->wk, ctx->info->compile_args, make_strf(ctx->wk, "-%c%s", frag->type, frag->data)); } else { L("skipping null pkgconf fragment: '%s'", frag->data); } break; } } ret: pkgconf_fragment_free(&list); return ret; } static bool apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *_ctx, int maxdepth) { struct pkgconf_lookup_ctx *ctx = _ctx; pkgconf_dependency_t *dep = world->required.head->data; pkgconf_pkg_t *pkg = dep->match; if (pkg != NULL && pkg->version != NULL) { strncpy(ctx->info->version, pkg->version, MAX_VERSION_LEN); } return true; } bool muon_pkgconf_lookup(struct workspace *wk, obj name, bool is_static, struct pkgconf_info *info) { if (!pkgconf_ctx.init) { if (!muon_pkgconf_init(wk)) { return false; } } int flags = 0; #ifdef _WIN32 flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; #endif if (is_static) { flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); } pkgconf_client_set_flags(&pkgconf_ctx.client, flags); bool ret = true; pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; pkgconf_queue_push(&pkgq, get_cstr(wk, name)); struct pkgconf_lookup_ctx ctx = { .wk = wk, .info = info, .name = name, .is_static = is_static }; if (!pkgconf_queue_apply(&pkgconf_ctx.client, &pkgq, apply_modversion, pkgconf_ctx.maxdepth, &ctx)) { ret = false; goto ret; } make_obj(wk, &info->compile_args, obj_array); make_obj(wk, &info->link_args, obj_array); make_obj(wk, &info->includes, obj_array); make_obj(wk, &info->libs, obj_array); make_obj(wk, &info->not_found_libs, obj_array); make_obj(wk, &ctx.libdirs, obj_array); ctx.apply_func = pkgconf_pkg_libs; if (!pkgconf_queue_apply(&pkgconf_ctx.client, &pkgq, apply_and_collect, pkgconf_ctx.maxdepth, &ctx)) { ret = false; goto ret; } // meson runs pkg-config to look for cflags, // which honors Requires.private if any cflags are requested. pkgconf_client_set_flags(&pkgconf_ctx.client, flags | PKGCONF_PKG_PKGF_SEARCH_PRIVATE); ctx.apply_func = pkgconf_pkg_cflags; if (!pkgconf_queue_apply(&pkgconf_ctx.client, &pkgq, apply_and_collect, pkgconf_ctx.maxdepth, &ctx)) { ret = false; goto ret; } pkgconf_client_set_flags(&pkgconf_ctx.client, flags); ret: pkgconf_queue_free(&pkgq); return ret; } struct pkgconf_get_variable_ctx { struct workspace *wk; const char *var; obj *res; }; static bool apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, void *_ctx, int maxdepth) { struct pkgconf_get_variable_ctx *ctx = _ctx; bool found = false; const char *var; pkgconf_dependency_t *dep = world->required.head->data; pkgconf_pkg_t *pkg = dep->match; if (pkg != NULL) { var = pkgconf_tuple_find(client, &pkg->vars, ctx->var); if (var != NULL) { *ctx->res = make_str(ctx->wk, var); found = true; } } return found; } bool muon_pkgconf_get_variable(struct workspace *wk, const char *pkg_name, const char *var, obj *res) { if (!pkgconf_ctx.init) { if (!muon_pkgconf_init(wk)) { return false; } } pkgconf_client_set_flags(&pkgconf_ctx.client, PKGCONF_PKG_PKGF_SEARCH_PRIVATE); pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; pkgconf_queue_push(&pkgq, pkg_name); bool ret = true; struct pkgconf_get_variable_ctx ctx = { .wk = wk, .res = res, .var = var, }; if (!pkgconf_queue_apply(&pkgconf_ctx.client, &pkgq, apply_variable, pkgconf_ctx.maxdepth, &ctx)) { ret = false; goto ret; } ret: pkgconf_queue_free(&pkgq); return ret; } muon-0.2.0/src/external/libpkgconf_null.c000066400000000000000000000010201441402162300204020ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "external/libpkgconf.h" #include "log.h" const bool have_libpkgconf = false; bool muon_pkgconf_lookup(struct workspace *wk, obj name, bool is_static, struct pkgconf_info *info) { LOG_W("libpkgconf not enabled"); return false; } bool muon_pkgconf_get_variable(struct workspace *wk, const char *pkg_name, const char *var, obj *res) { LOG_W("libpkgconf not enabled"); return false; } muon-0.2.0/src/external/meson.build000066400000000000000000000017741441402162300172500ustar00rootroot00000000000000# SPDX-FileCopyrightText: Stone Tickle # SPDX-License-Identifier: GPL-3.0-only external_deps = [] dep_sources = [] dep_dict = {} foreach d : [ ['libcurl'], ['libarchive'], [ 'libpkgconf', { 'fallback': ['pkgconf', 'dep_libpkgconf'], 'default_options': ['pkgconf:tests=false', 'warning_level=1'], }, ], ['samurai', {'fallback': ['samurai', 'libsamu_dep']}], ['bestline'], ] name = d[0] kwargs = d.get(1, {}) if get_option('static') kwargs += {'static': true} endif dep = dependency(name, required: get_option(name), kwargs: kwargs) summary(name, dep.found()) dep_dict += {name: dep.found()} if dep.found() external_deps += [dep] dep_sources += [files((name + '.c'))] else dep_sources += [files((name + '_null.c'))] endif endforeach if get_option('static') and dep_dict['libcurl'] external_deps += dependency('libbrotlidec', static: true) endif muon-0.2.0/src/external/samurai.c000066400000000000000000000006371441402162300167100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "external/samurai.h" #include "platform/filesystem.h" #include "platform/path.h" const bool have_samurai = true; bool muon_samu(uint32_t argc, char *const argv[]) { return samu_main(argc, (char **)argv) == 0; } muon-0.2.0/src/external/samurai_null.c000066400000000000000000000004711441402162300177360ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "external/samurai.h" #include "log.h" const bool have_samurai = false; bool muon_samu(uint32_t argc, char *const argv[]) { LOG_W("samurai not enabled"); return false; } muon-0.2.0/src/formats/000077500000000000000000000000001441402162300147265ustar00rootroot00000000000000muon-0.2.0/src/formats/ini.c000066400000000000000000000056331441402162300156600ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "error.h" #include "formats/lines.h" #include "formats/ini.h" #include "iterator.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.h" struct ini_parse_ctx { struct source src; const char *comment_chars; bool keyval; void *octx; char *sect; inihcb cb; uint32_t line; bool success; }; static bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } static bool line_is_whitespace(const char *c) { for (; *c; ++c) { if (!is_whitespace(*c)) { return false; } } return true; } static enum iteration_result ini_parse_line_cb(void *_ctx, char *line, size_t len) { struct ini_parse_ctx *ctx = _ctx; char *ptr, *key, *val; if (!*line || strchr(ctx->comment_chars, *line) || line_is_whitespace(line)) { goto done_with_line; } else if (!ctx->keyval && *line == '[') { if (!(ptr = strchr(line, ']'))) { error_messagef(&ctx->src, ctx->line, strlen(line) + 1, log_error, "expected ']'"); ctx->success = false; goto done_with_line; } *ptr = '\0'; ctx->sect = line + 1; if (!ctx->cb(ctx->octx, &ctx->src, ctx->sect, NULL, NULL, ctx->line)) { ctx->success = false; } goto done_with_line; } if (!(ptr = strchr(line, '='))) { if (!ctx->keyval) { error_messagef(&ctx->src, ctx->line, strlen(line) + 1, log_error, "expected '='"); ctx->success = false; } goto done_with_line; } *ptr = '\0'; key = line; val = ptr - 1; while (is_whitespace(*val)) { *val = '\0'; --val; } val = ptr + 1; while (is_whitespace(*val)) { ++val; } if (!ctx->cb(ctx->octx, &ctx->src, ctx->sect, key, val, ctx->line)) { ctx->success = false; } done_with_line: if (!ctx->success) { return ir_done; } ++ctx->line; return ir_cont; } bool ini_reparse(const char *path, const struct source *src, char *buf, inihcb cb, void *octx) { struct ini_parse_ctx ctx = { .comment_chars = ";#", .octx = octx, .cb = cb, .line = 1, .success = true, .src = *src, }; memcpy(buf, ctx.src.src, ctx.src.len); each_line(buf, ctx.src.len, &ctx, ini_parse_line_cb); return ctx.success; } bool ini_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx) { if (!fs_read_entire_file(path, src)) { return false; } *buf = z_calloc(src->len, 1); return ini_reparse(path, src, *buf, cb, octx); } bool keyval_parse(const char *path, struct source *src, char **buf, inihcb cb, void *octx) { if (!fs_read_entire_file(path, src)) { return false; } *buf = z_calloc(src->len, 1); struct ini_parse_ctx ctx = { .comment_chars = "#", .keyval = true, .octx = octx, .cb = cb, .line = 1, .success = true, .src = *src, }; memcpy(*buf, ctx.src.src, ctx.src.len); each_line(*buf, ctx.src.len, &ctx, ini_parse_line_cb); return ctx.success; } muon-0.2.0/src/formats/lines.c000066400000000000000000000010121441402162300161760ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "formats/lines.h" void each_line(char *buf, uint64_t len, void *ctx, each_line_callback cb) { char *line, *b; line = buf; while ((b = strchr(line, '\n'))) { *b = '\0'; if (cb(ctx, line, b - line) != ir_cont) { return; } line = b + 1; if ((size_t)(line - buf) >= len) { return; } } if (*line) { cb(ctx, line, strlen(line)); } } muon-0.2.0/src/formats/tap.c000066400000000000000000000037041441402162300156620ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "formats/lines.h" #include "formats/tap.h" #include "lang/string.h" #include "log.h" struct tap_parse_ctx { struct tap_parse_result *res; bool have_plan; bool bail_out; }; static enum iteration_result tap_parse_line_cb(void *_ctx, char *line, size_t _) { struct tap_parse_ctx *ctx = _ctx; struct str l = WKSTR(line), rest; bool ok; if (str_startswith(&l, &WKSTR("1..")) && l.len > 3) { struct str i = { .s = &l.s[3], l.len - 3 }; int64_t plan_count; if (str_to_i(&i, &plan_count) && plan_count > 0) { ctx->have_plan = true; ctx->res->total = plan_count; } return ir_cont; } else if (str_startswith(&l, &WKSTR("Bail out!"))) { ctx->bail_out = true; return ir_cont; } else if (str_startswith(&l, &WKSTR("ok"))) { ok = true; rest = (struct str) { .s = &l.s[2], .len = l.len - 2 }; } else if (str_startswith(&l, &WKSTR("not ok"))) { ok = false; rest = (struct str) { .s = &l.s[6], .len = l.len - 6 }; } else { return ir_cont; } enum { none, todo, skip, } directive = none; { char *directive_str; if ((directive_str = strstr(rest.s, " # "))) { directive_str += 3; if (strncasecmp(directive_str, "todo", 4) == 0) { directive = todo; } else if (strncasecmp(directive_str, "skip", 4) == 0) { directive = skip; } } } if (directive == skip) { ++ctx->res->skip; return ir_cont; } if (ok) { ++ctx->res->pass; } else { if (directive == todo) { ++ctx->res->skip; } else { ++ctx->res->fail; } } return ir_cont; } void tap_parse(char *buf, uint64_t buf_len, struct tap_parse_result *res) { struct tap_parse_ctx ctx = { .res = res }; each_line(buf, buf_len, &ctx, tap_parse_line_cb); if (!ctx.have_plan) { res->total = res->pass + res->skip + res->fail; } res->all_ok = res->total == res->pass + res->skip; } muon-0.2.0/src/functions/000077500000000000000000000000001441402162300152635ustar00rootroot00000000000000muon-0.2.0/src/functions/array.c000066400000000000000000000054251441402162300165530ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/array.h" #include "lang/interpreter.h" #include "log.h" static bool func_array_length(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, get_obj_array(wk, rcvr)->len); return true; } static bool func_array_get(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_number }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } int64_t i = get_obj_number(wk, an[0].val); if (!bounds_adjust(wk, get_obj_array(wk, rcvr)->len, &i)) { if (ao[0].set) { *res = ao[0].val; } else { interp_error(wk, an[0].node, "index out of bounds"); return false; } } else { obj_array_index(wk, rcvr, i, res); } return true; } struct array_contains_ctx { obj item; bool found; }; static enum iteration_result array_contains_iter(struct workspace *wk, void *_ctx, obj val) { struct array_contains_ctx *ctx = _ctx; if (get_obj_type(wk, val) == obj_array) { obj_array_foreach(wk, val, ctx, array_contains_iter); if (ctx->found) { return ir_done; } } if (obj_equal(wk, val, ctx->item)) { ctx->found = true; return ir_done; } return ir_cont; } static bool func_array_contains(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } struct array_contains_ctx ctx = { .item = an[0].val }; obj_array_foreach(wk, rcvr, &ctx, array_contains_iter); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ctx.found); return true; } static bool func_array_delete(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_number }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } int64_t idx = get_obj_number(wk, an[0].val); if (!boundscheck(wk, an[0].node, get_obj_array(wk, rcvr)->len, &idx)) { return false; } obj_array_del(wk, rcvr, idx); return true; } const struct func_impl_name impl_tbl_array[] = { { "length", func_array_length, tc_number, true }, { "get", func_array_get, tc_any, true }, { "contains", func_array_contains, tc_bool, true }, { NULL, NULL }, }; const struct func_impl_name impl_tbl_array_internal[] = { { "length", func_array_length, tc_number, true }, { "get", func_array_get, tc_any, true }, { "contains", func_array_contains, tc_bool, true }, { "delete", func_array_delete, }, { NULL, NULL }, }; muon-0.2.0/src/functions/boolean.c000066400000000000000000000021761441402162300170540ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/common.h" #include "functions/boolean.h" #include "lang/interpreter.h" #include "log.h" static bool func_boolean_to_string(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } if (get_obj_bool(wk, rcvr)) { *res = ao[0].set ? ao[0].val : make_str(wk, "true"); } else { *res = ao[1].set ? ao[1].val : make_str(wk, "false"); } return true; } static bool func_boolean_to_int(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } int32_t val = get_obj_bool(wk, rcvr) ? 1 : 0; make_obj(wk, res, obj_number); set_obj_number(wk, *res, val); return true; } const struct func_impl_name impl_tbl_boolean[] = { { "to_int", func_boolean_to_int, tc_number }, { "to_string", func_boolean_to_string, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/both_libs.c000066400000000000000000000026771441402162300174100ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "buf_size.h" #include "functions/both_libs.h" #include "functions/build_target.h" #include "functions/common.h" #include "lang/interpreter.h" #include "log.h" static bool func_both_libs_get_shared_lib(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_both_libs(wk, rcvr)->dynamic_lib; return true; } static bool func_both_libs_get_static_lib(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_both_libs(wk, rcvr)->static_lib; return true; } static obj both_libs_rcvr_transform(struct workspace *wk, obj rcvr) { return get_obj_both_libs(wk, rcvr)->dynamic_lib; } void both_libs_build_impl_tbl(void) { uint32_t i; for (i = 0; impl_tbl_build_target[i].name; ++i) { struct func_impl_name tmp = impl_tbl_build_target[i]; tmp.rcvr_transform = both_libs_rcvr_transform; impl_tbl_both_libs[i] = tmp; } } struct func_impl_name impl_tbl_both_libs[] = { [ARRAY_LEN(impl_tbl_build_target) - 1] = { "get_shared_lib", func_both_libs_get_shared_lib, tc_build_target }, { "get_static_lib", func_both_libs_get_static_lib, tc_build_target }, { NULL, NULL }, }; muon-0.2.0/src/functions/build_target.c000066400000000000000000000170231441402162300200770ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "coerce.h" #include "error.h" #include "functions/build_target.h" #include "functions/common.h" #include "functions/generator.h" #include "lang/interpreter.h" #include "log.h" #include "platform/path.h" bool tgt_src_to_object_path(struct workspace *wk, const struct obj_build_target *tgt, obj src_file, bool relative, struct sbuf *res) { obj src = *get_obj_file(wk, src_file); SBUF(private_path_rel); SBUF(rel); const char *base, *private_path = get_cstr(wk, tgt->private_path); if (relative) { path_relative_to(wk, &private_path_rel, wk->build_root, private_path); private_path = private_path_rel.buf; } if (path_is_subpath(get_cstr(wk, tgt->private_path), get_cstr(wk, src))) { // file is a source from a generated list base = get_cstr(wk, tgt->private_path); } else if (path_is_subpath(get_cstr(wk, tgt->build_dir), get_cstr(wk, src))) { // file is a generated source from custom_target / configure_file base = get_cstr(wk, tgt->build_dir); } else if (path_is_subpath(get_cstr(wk, tgt->cwd), get_cstr(wk, src))) { // file is in target cwd base = get_cstr(wk, tgt->cwd); } else if (path_is_subpath(wk->source_root, get_cstr(wk, src))) { // file is in source root base = wk->source_root; } else { // outside the source root base = NULL; } if (base) { path_relative_to(wk, &rel, base, get_cstr(wk, src)); } else { path_copy(wk, &rel, get_cstr(wk, src)); uint32_t i; for (i = 0; i < rel.len; ++i) { if (rel.buf[i] == PATH_SEP) { rel.buf[i] = '_'; } } } path_join(wk, res, private_path, rel.buf); sbuf_pushs(wk, res, ".o"); return true; } static bool func_build_target_name(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_build_target(wk, rcvr)->name; return true; } static bool func_build_target_full_path(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } struct obj_build_target *tgt = get_obj_build_target(wk, rcvr); *res = tgt->build_path; return true; } struct build_target_extract_objects_ctx { uint32_t err_node; struct obj_build_target *tgt; obj tgt_id; obj *res; }; static enum iteration_result build_target_extract_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct build_target_extract_objects_ctx *ctx = _ctx; obj file; enum obj_type t = get_obj_type(wk, val); if (!typecheck(wk, ctx->err_node, val, tc_file | tc_string | tc_custom_target | tc_generated_list)) { return false; } switch (t) { case obj_string: { if (!coerce_string_to_file(wk, get_cstr(wk, ctx->tgt->cwd), val, &file)) { return ir_err; } break; } case obj_file: file = val; break; case obj_custom_target: { struct obj_custom_target *tgt = get_obj_custom_target(wk, val); if (!obj_array_flatten_one(wk, tgt->output, &file)) { interp_error(wk, ctx->err_node, "cannot coerce custom_target with multiple outputs to file"); return ir_err; } break; } case obj_generated_list: { obj res; if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, false, &res)) { return ir_err; } if (!obj_array_foreach(wk, res, ctx, build_target_extract_objects_iter)) { return ir_err; } return ir_cont; } default: UNREACHABLE_RETURN; } enum compiler_language l; if (!filename_to_compiler_language(get_file_path(wk, file), &l)) { return ir_cont; } switch (l) { case compiler_language_cpp_hdr: case compiler_language_c_hdr: case compiler_language_c_obj: // skip non-compileable sources return ir_cont; case compiler_language_assembly: case compiler_language_nasm: case compiler_language_c: case compiler_language_cpp: case compiler_language_llvm_ir: case compiler_language_objc: break; case compiler_language_null: case compiler_language_count: UNREACHABLE; } if (!obj_array_in(wk, ctx->tgt->src, file)) { interp_error(wk, ctx->err_node, "%o is not in target sources (%o)", file, ctx->tgt->src); return ir_err; } SBUF(dest_path); if (!tgt_src_to_object_path(wk, ctx->tgt, file, false, &dest_path)) { return ir_err; } obj new_file; make_obj(wk, &new_file, obj_file); *get_obj_file(wk, new_file) = sbuf_into_str(wk, &dest_path); obj_array_push(wk, *ctx->res, new_file); return ir_cont; } static bool build_target_extract_objects(struct workspace *wk, obj rcvr, uint32_t err_node, obj *res, obj arr) { make_obj(wk, res, obj_array); struct build_target_extract_objects_ctx ctx = { .err_node = err_node, .res = res, .tgt = get_obj_build_target(wk, rcvr), .tgt_id = rcvr, }; return obj_array_foreach_flat(wk, arr, &ctx, build_target_extract_objects_iter); } static bool func_build_target_extract_objects(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_string | tc_file | tc_custom_target | tc_generated_list }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } return build_target_extract_objects(wk, rcvr, an[0].node, res, an[0].val); } static enum iteration_result build_target_extract_all_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct build_target_extract_objects_ctx *ctx = _ctx; return build_target_extract_objects_iter(wk, ctx, val); } bool build_target_extract_all_objects(struct workspace *wk, uint32_t err_node, obj rcvr, obj *res, bool recursive) { make_obj(wk, res, obj_array); struct build_target_extract_objects_ctx ctx = { .err_node = err_node, .res = res, .tgt = get_obj_build_target(wk, rcvr), .tgt_id = rcvr, }; if (!obj_array_foreach_flat(wk, ctx.tgt->src, &ctx, build_target_extract_all_objects_iter)) { return false; } if (recursive) { obj_array_extend(wk, *res, ctx.tgt->objects); } return true; } static bool func_build_target_extract_all_objects(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { enum kwargs { kw_recursive, }; struct args_kw akw[] = { [kw_recursive] = { "recursive", obj_bool }, 0 }; if (!interp_args(wk, args_node, NULL, NULL, akw)) { return false; } bool recursive = akw[kw_recursive].set ? get_obj_bool(wk, akw[kw_recursive].val) : false; return build_target_extract_all_objects(wk, args_node, rcvr, res, recursive); } static bool func_build_target_private_dir_include(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_include_directory); struct obj_include_directory *inc = get_obj_include_directory(wk, *res); inc->path = get_obj_build_target(wk, rcvr)->private_path; return true; } static bool func_build_target_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, true); return true; } const struct func_impl_name impl_tbl_build_target[] = { { "extract_all_objects", func_build_target_extract_all_objects, tc_array }, { "extract_objects", func_build_target_extract_objects, tc_array }, { "found", func_build_target_found, tc_bool }, { "full_path", func_build_target_full_path, tc_string }, { "name", func_build_target_name, tc_string }, { "path", func_build_target_full_path, tc_string }, { "private_dir_include", func_build_target_private_dir_include, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/common.c000066400000000000000000000622071441402162300167260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "functions/array.h" #include "functions/boolean.h" #include "functions/both_libs.h" #include "functions/build_target.h" #include "functions/common.h" #include "functions/compiler.h" #include "functions/configuration_data.h" #include "functions/custom_target.h" #include "functions/dependency.h" #include "functions/dict.h" #include "functions/disabler.h" #include "functions/environment.h" #include "functions/external_program.h" #include "functions/feature_opt.h" #include "functions/file.h" #include "functions/generator.h" #include "functions/kernel.h" #include "functions/machine.h" #include "functions/meson.h" #include "functions/modules.h" #include "functions/number.h" #include "functions/run_result.h" #include "functions/source_configuration.h" #include "functions/source_set.h" #include "functions/string.h" #include "functions/subproject.h" #include "lang/interpreter.h" #include "log.h" #include "tracy.h" // When true, disable functions with the .fuzz_unsafe attribute set to true. // This is useful when running `muon internal eval` on randomly generated // files, where you don't want to accidentally execute `run_command('rm', // '-rf', '/')` for example bool disable_fuzz_unsafe_functions = false; // HACK: this is pretty terrible, but the least intrusive way to handle // disablers in function arguments as they are currently implemented. When // interp_args sees a disabler, it sets this flag, and "fails". In the // function error handler we check this flag and don't raise an error if it is // set but instead return disabler. static bool disabler_among_args = false; // HACK: we also need this for the is_disabler() function :( bool disabler_among_args_immunity = false; // HACK: This works like disabler_among_args kind of. These opts should only // ever be set by analyze_function(). static struct analyze_function_opts { bool do_analyze; bool pure_function; bool encountered_error; bool set_variable_special; // set to true if the function is set_variable() bool dump_signature; // used when dumping funciton signatures } analyze_function_opts; static bool interp_args_interp_node(struct workspace *wk, uint32_t arg_node, obj *res) { bool was_immune = disabler_among_args_immunity; disabler_among_args_immunity = false; if (!wk->interp_node(wk, arg_node, res)) { return false; } disabler_among_args_immunity = was_immune; return true; } static bool next_arg(struct ast *ast, uint32_t *arg_node, uint32_t *kwarg_node, const char **kw, struct node **args) { if (!*args || (*args)->type == node_empty) { return false; } assert((*args)->type == node_argument); if ((*args)->subtype == arg_kwarg) { *kw = get_node(ast, (*args)->l)->dat.s; *kwarg_node = (*args)->l; *arg_node = (*args)->r; } else { *kw = NULL; *arg_node = (*args)->l; } /* L("got arg %s:%s", *kw, node_to_s(*arg)); */ if ((*args)->chflg & node_child_c) { *args = get_node(ast, (*args)->c); } else { *args = NULL; } return true; } struct function_signature { const char *name, *posargs, *varargs, *optargs, *kwargs, *returns; bool is_method; const struct func_impl_name *impl; }; struct { struct darr sigs; } function_sig_dump; static const char * dump_type(struct workspace *wk, type_tag type) { obj types = typechecking_type_to_arr(wk, type); obj typestr, sep = make_str(wk, "|"); obj_array_join(wk, false, types, sep, &typestr); if (type & ARG_TYPE_ARRAY_OF) { obj_array_push(wk, types, make_strf(wk, "list[%s]", get_cstr(wk, typestr))); obj sorted; obj_array_sort(wk, NULL, types, obj_array_sort_by_str, &sorted); obj_array_join(wk, false, sorted, sep, &typestr); } return get_cstr(wk, typestr); } static int32_t darr_sort_by_string(const void *a, const void *b, void *_ctx) { return strcmp(*(const char **)a, *(const char **)b); } static void dump_function_signature(struct workspace *wk, struct args_norm posargs[], struct args_norm optargs[], struct args_kw kwargs[]) { uint32_t i; struct function_signature *sig = darr_get(&function_sig_dump.sigs, function_sig_dump.sigs.len - 1); obj s; if (posargs) { s = make_str(wk, ""); for (i = 0; posargs[i].type != ARG_TYPE_NULL; ++i) { if (posargs[i].type & ARG_TYPE_GLOB) { sig->varargs = get_cstr(wk, make_strf(wk, " %s\n", dump_type(wk, posargs[i].type))); continue; } str_appf(wk, s, " %s\n", dump_type(wk, posargs[i].type)); } const char *ts = get_cstr(wk, s); if (*ts) { sig->posargs = ts; } } if (optargs) { s = make_str(wk, ""); for (i = 0; optargs[i].type != ARG_TYPE_NULL; ++i) { str_appf(wk, s, " %s\n", dump_type(wk, optargs[i].type)); } sig->optargs = get_cstr(wk, s); } if (kwargs) { struct darr kwargs_list; darr_init(&kwargs_list, 8, sizeof(char *)); for (i = 0; kwargs[i].key; ++i) { const char *v = get_cstr(wk, make_strf(wk, " %s: %s\n", kwargs[i].key, dump_type(wk, kwargs[i].type))); darr_push(&kwargs_list, &v); } darr_sort(&kwargs_list, NULL, darr_sort_by_string); s = make_str(wk, ""); for (i = 0; i < kwargs_list.len; ++i) { str_app(wk, s, *(const char **)darr_get(&kwargs_list, i)); } sig->kwargs = get_cstr(wk, s); darr_destroy(&kwargs_list); } } static const char * arity_to_s(struct args_norm positional_args[], struct args_norm optional_positional_args[], struct args_kw keyword_args[]) { static char buf[BUF_SIZE_2k + 1] = { 0 }; uint32_t i, bufi = 0; bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "(signature: "); if (positional_args) { bool glob = false; for (i = 0; positional_args[i].type != ARG_TYPE_NULL; ++i) { if (positional_args[i].type & ARG_TYPE_GLOB) { glob = true; break; } } if (i) { bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "%d positional", i); } if (glob) { if (i) { bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, ", "); } bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "varargs"); } } if (optional_positional_args) { for (i = 0; optional_positional_args[i].type != ARG_TYPE_NULL; ++i) { } if (positional_args) { bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, ", "); } bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "%d optional", i); } if (keyword_args) { for (i = 0; keyword_args[i].key; ++i) { } if (positional_args || optional_positional_args) { bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, ", "); } bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "%d keyword", i); } if (!positional_args && !optional_positional_args && !keyword_args) { bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, "0 arguments"); } bufi += snprintf(&buf[bufi], BUF_SIZE_2k - bufi, ")"); return buf; } struct typecheck_function_arg_ctx { uint32_t err_node; obj arr; type_tag type; }; static enum iteration_result typecheck_function_arg_iter(struct workspace *wk, void *_ctx, obj val) { struct typecheck_function_arg_ctx *ctx = _ctx; if (!typecheck(wk, ctx->err_node, val, ctx->type)) { return ir_err; } obj_array_push(wk, ctx->arr, val); return ir_cont; } static enum iteration_result typecheck_function_arg_check_disabler_iter(struct workspace *wk, void *_ctx, obj val) { bool *among = _ctx; if (val == disabler_id) { *among = true; return ir_done; } return ir_cont; } static bool typecheck_function_arg(struct workspace *wk, uint32_t err_node, obj *val, type_tag type) { if (!disabler_among_args_immunity) { if (*val == disabler_id) { disabler_among_args = true; return false; } else if (get_obj_type(wk, *val) == obj_array) { bool among = false; obj_array_foreach_flat(wk, *val, &among, typecheck_function_arg_check_disabler_iter); if (among) { disabler_among_args = true; return false; } } } bool array_of = false; if (type & ARG_TYPE_ARRAY_OF) { array_of = true; type &= ~ARG_TYPE_ARRAY_OF; } assert((type & obj_typechecking_type_tag) || type < obj_type_count); // If obj_file or tc_file is requested, and the arugment is an array of // length 1, try to unpack it. if (!array_of && (type == obj_file || (type & tc_file) == tc_file)) { if (get_obj_type(wk, *val) == obj_array && get_obj_array(wk, *val)->len == 1) { obj i0; obj_array_index(wk, *val, 0, &i0); if (get_obj_type(wk, i0) == obj_file) { *val = i0; } } else if (get_obj_type(wk, *val) == obj_typeinfo && (get_obj_typeinfo(wk, *val)->type & tc_array) == tc_array) { return true; } } if (!array_of) { return typecheck(wk, err_node, *val, type); } struct typecheck_function_arg_ctx ctx = { .err_node = err_node, .type = type, }; make_obj(wk, &ctx.arr, obj_array); if (get_obj_type(wk, *val) == obj_array) { if (!obj_array_foreach_flat(wk, *val, &ctx, typecheck_function_arg_iter)) { return false; } } else if (get_obj_type(wk, *val) == obj_typeinfo && (get_obj_typeinfo(wk, *val)->type & tc_array) == tc_array) { return true; } else { if (!typecheck_function_arg_iter(wk, &ctx, *val)) { return false; } } *val = ctx.arr; return true; } #define ARITY arity_to_s(positional_args, optional_positional_args, keyword_args) static bool process_kwarg(struct workspace *wk, uint32_t kwarg_node, uint32_t arg_node, struct args_kw *keyword_args, const char *kw, obj val) { uint32_t i; for (i = 0; keyword_args[i].key; ++i) { if (strcmp(kw, keyword_args[i].key) == 0) { break; } } if (!keyword_args[i].key) { interp_error(wk, kwarg_node, "invalid kwarg: '%s'", kw); return false; } if (!typecheck_function_arg(wk, arg_node, &val, keyword_args[i].type)) { return false; } else if (keyword_args[i].set) { interp_error(wk, arg_node, "keyword argument '%s' set twice", keyword_args[i].key); return false; } keyword_args[i].val = val; keyword_args[i].node = kwarg_node; keyword_args[i].set = true; return true; } struct process_kwarg_dict_ctx { uint32_t kwarg_node; uint32_t arg_node; struct args_kw *keyword_args; }; static enum iteration_result process_kwarg_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct process_kwarg_dict_ctx *ctx = _ctx; if (!process_kwarg(wk, ctx->kwarg_node, ctx->arg_node, ctx->keyword_args, get_cstr(wk, key), val)) { return ir_err; } return ir_cont; } static bool obj_tainted_by_typeinfo(struct workspace *wk, obj o); static enum iteration_result obj_tainted_by_typeinfo_dict_iter(struct workspace *wk, void *_ctx, obj k, obj v) { if (obj_tainted_by_typeinfo(wk, k) || obj_tainted_by_typeinfo(wk, v)) { return ir_err; } return ir_cont; } static enum iteration_result obj_tainted_by_typeinfo_array_iter(struct workspace *wk, void *_ctx, obj v) { if (obj_tainted_by_typeinfo(wk, v)) { return ir_err; } return ir_cont; } static bool obj_tainted_by_typeinfo(struct workspace *wk, obj o) { if (!o) { return true; } switch (get_obj_type(wk, o)) { case obj_typeinfo: return true; case obj_array: return !obj_array_foreach(wk, o, NULL, obj_tainted_by_typeinfo_array_iter); case obj_dict: return !obj_dict_foreach(wk, o, NULL, obj_tainted_by_typeinfo_dict_iter); default: return false; } } bool interp_args(struct workspace *wk, uint32_t args_node, struct args_norm positional_args[], struct args_norm optional_positional_args[], struct args_kw keyword_args[]) { if (analyze_function_opts.dump_signature) { dump_function_signature(wk, positional_args, optional_positional_args, keyword_args); return false; } const char *kw; uint32_t arg_node, kwarg_node; uint32_t i, stage; struct args_norm *an[2] = { positional_args, optional_positional_args }; struct node *args = get_node(wk->ast, args_node); for (stage = 0; stage < 2; ++stage) { if (!an[stage]) { continue; } for (i = 0; an[stage][i].type != ARG_TYPE_NULL; ++i) { if (an[stage][i].type & ARG_TYPE_GLOB) { assert(stage == 0 && "glob args must not be optional"); assert(!optional_positional_args && "glob args cannot be followed by optional args"); assert(an[stage][i + 1].type == ARG_TYPE_NULL && "glob args must come last"); assert(!(an[stage][i].type & ARG_TYPE_ARRAY_OF) && "glob args are implicitly ARG_TYPE_ARRAY_OF"); an[stage][i].type &= ~ARG_TYPE_GLOB; bool set_arg_node = false; make_obj(wk, &an[stage][i].val, obj_array); an[stage][i].set = true; while (next_arg(wk->ast, &arg_node, &kwarg_node, &kw, &args)) { if (kw) { goto kwargs; } if (!set_arg_node) { an[stage][i].node = arg_node; set_arg_node = true; } obj val; if (!interp_args_interp_node(wk, arg_node, &val)) { return false; } // If we get an array, but that isn't a valid type here, flatten it. if ((get_obj_type(wk, val) == obj_array || (get_obj_type(wk, val) == obj_typeinfo && (get_obj_typeinfo(wk, val)->type & tc_array) == tc_array)) && !(an[stage][i].type == tc_any || an[stage][i].type == obj_array || (an[stage][i].type & tc_array) == tc_array) ) { if (get_obj_type(wk, val) == obj_typeinfo) { // TODO typecheck subtype obj_array_push(wk, an[stage][i].val, val); } else { if (!typecheck_function_arg(wk, arg_node, &val, ARG_TYPE_ARRAY_OF | an[stage][i].type)) { return false; } obj_array_extend_nodup(wk, an[stage][i].val, val); } } else { if (!typecheck_function_arg(wk, arg_node, &val, an[stage][i].type)) { return false; } obj_array_push(wk, an[stage][i].val, val); } } if (!set_arg_node) { an[stage][i].node = args_node; } continue; } if (!next_arg(wk->ast, &arg_node, &kwarg_node, &kw, &args)) { if (stage == 0) { // required interp_error(wk, args_node, "missing arguments %s", ARITY); return false; } else if (stage == 1) { // optional goto end; } } if (kw) { if (stage == 0) { interp_error(wk, kwarg_node, "unexpected kwarg before required arguments %s", ARITY); return false; } goto kwargs; } if (!interp_args_interp_node(wk, arg_node, &an[stage][i].val)) { return false; } if (!typecheck_function_arg(wk, arg_node, &an[stage][i].val, an[stage][i].type)) { return false; } an[stage][i].node = arg_node; an[stage][i].set = true; } } if (keyword_args) { while (next_arg(wk->ast, &arg_node, &kwarg_node, &kw, &args)) { goto process_kwarg; kwargs: if (!keyword_args) { interp_error(wk, args_node, "this function does not accept kwargs %s", ARITY); return false; } process_kwarg: if (!kw) { interp_error(wk, arg_node, "non-kwarg after kwargs %s", ARITY); return false; } obj val; if (!interp_args_interp_node(wk, arg_node, &val)) { return false; } if (strcmp(kw, "kwargs") == 0) { if (!typecheck(wk, arg_node, val, obj_dict)) { return false; } struct process_kwarg_dict_ctx ctx = { .kwarg_node = kwarg_node, .arg_node = arg_node, .keyword_args = keyword_args }; if (get_obj_type(wk, val) != obj_typeinfo) { if (!obj_dict_foreach(wk, val, &ctx, process_kwarg_dict_iter)) { return false; } } } else { if (!process_kwarg(wk, kwarg_node, arg_node, keyword_args, kw, val)) { return false; } } } for (i = 0; keyword_args[i].key; ++i) { if (keyword_args[i].required && !keyword_args[i].set) { interp_error(wk, args_node, "missing required kwarg: %s", keyword_args[i].key); return false; } } } else if (next_arg(wk->ast, &arg_node, &kwarg_node, &kw, &args)) { if (kw) { interp_error(wk, kwarg_node, "this function does not accept kwargs %s", ARITY); } else { interp_error(wk, arg_node, "too many arguments %s", ARITY); } return false; } end: if (analyze_function_opts.do_analyze) { bool typeinfo_among_args = false; for (stage = 0; stage < 2; ++stage) { if (!an[stage]) { continue; } for (i = 0; an[stage][i].type != ARG_TYPE_NULL; ++i) { if (!an[stage][i].set) { continue; } if (analyze_function_opts.set_variable_special && stage == 0 && i == 1) { // allow set_variable() to be called // even if its second argument is // impure continue; } if (obj_tainted_by_typeinfo(wk, an[stage][i].val)) { typeinfo_among_args = true; break; } } } if (!typeinfo_among_args && keyword_args) { for (i = 0; keyword_args[i].key; ++i) { if (!keyword_args[i].set) { continue; } if (obj_tainted_by_typeinfo(wk, keyword_args[i].val)) { typeinfo_among_args = true; break; } } } if (typeinfo_among_args) { analyze_function_opts.pure_function = false; } if (analyze_function_opts.pure_function) { return true; } analyze_function_opts.encountered_error = false; // // if we are analyzing arguments only return false to halt the // function return false; } return true; } const struct func_impl_name *kernel_func_tbl[language_mode_count] = { impl_tbl_kernel, impl_tbl_kernel_internal, impl_tbl_kernel_opts, }; const struct func_impl_name *func_tbl[obj_type_count][language_mode_count] = { [obj_meson] = { impl_tbl_meson, }, [obj_subproject] = { impl_tbl_subproject }, [obj_number] = { impl_tbl_number, impl_tbl_number, }, [obj_dependency] = { impl_tbl_dependency }, [obj_machine] = { impl_tbl_machine, impl_tbl_machine }, [obj_compiler] = { impl_tbl_compiler }, [obj_feature_opt] = { impl_tbl_feature_opt }, [obj_run_result] = { impl_tbl_run_result, impl_tbl_run_result }, [obj_string] = { impl_tbl_string, impl_tbl_string }, [obj_dict] = { impl_tbl_dict, impl_tbl_dict }, [obj_external_program] = { impl_tbl_external_program, impl_tbl_external_program }, [obj_configuration_data] = { impl_tbl_configuration_data, impl_tbl_configuration_data }, [obj_custom_target] = { impl_tbl_custom_target }, [obj_file] = { impl_tbl_file, impl_tbl_file }, [obj_bool] = { impl_tbl_boolean, impl_tbl_boolean }, [obj_array] = { impl_tbl_array, impl_tbl_array_internal }, [obj_build_target] = { impl_tbl_build_target }, [obj_environment] = { impl_tbl_environment, impl_tbl_environment }, [obj_disabler] = { impl_tbl_disabler, impl_tbl_disabler }, [obj_generator] = { impl_tbl_generator, }, [obj_both_libs] = { impl_tbl_both_libs, }, [obj_source_set] = { impl_tbl_source_set, }, [obj_source_configuration] = { impl_tbl_source_configuration, }, [obj_module] = { impl_tbl_module, } }; void build_func_impl_tables(void) { both_libs_build_impl_tbl(); } const struct func_impl_name * func_lookup(const struct func_impl_name *impl_tbl, const char *name) { uint32_t i; for (i = 0; impl_tbl[i].name; ++i) { if (strcmp(impl_tbl[i].name, name) == 0) { return &impl_tbl[i]; } } return NULL; } const char * func_name_str(bool have_rcvr, enum obj_type rcvr_type, const char *name) { static char buf[256]; if (have_rcvr) { snprintf(buf, 256, "method %s.%s()", obj_type_to_s(rcvr_type), name); } else { snprintf(buf, 256, "function %s()", name); } return buf; } bool builtin_run(struct workspace *wk, bool have_rcvr, obj rcvr_id, uint32_t node_id, obj *res) { const char *name; enum obj_type rcvr_type = 0; uint32_t args_node, name_node; struct node *n = get_node(wk->ast, node_id); const struct func_impl_name *impl_tbl; if (have_rcvr && !rcvr_id) { interp_error(wk, n->r, "tried to call function on null"); return false; } if (have_rcvr) { name_node = n->r; args_node = n->c; rcvr_type = get_obj_type(wk, rcvr_id); impl_tbl = func_tbl[rcvr_type][wk->lang_mode]; } else { assert(n->chflg & node_child_l); name_node = n->l; args_node = n->r; impl_tbl = kernel_func_tbl[wk->lang_mode]; } const struct func_impl_name *fi; name = get_node(wk->ast, name_node)->dat.s; if (have_rcvr && rcvr_type == obj_module) { struct obj_module *m = get_obj_module(wk, rcvr_id); enum module mod = m->module; if (!m->found && strcmp(name, "found") != 0) { interp_error(wk, name_node, "invalid attempt to use not-found module"); return false; } else if (!(fi = module_func_lookup(wk, name, mod))) { if (!m->has_impl) { interp_error(wk, name_node, "module '%s' is unimplemented,\n" " If you would like to make your build files portable to muon, use" " `import('%s', required: false)`, and then check" " the .found() method before use." , module_names[mod] , module_names[mod] ); return false; } else { interp_error(wk, name_node, "%s not found in module %s", func_name_str(false, 0, name), module_names[mod]); return false; } } } else { if (!impl_tbl) { interp_error(wk, name_node, "%s not found", func_name_str(true, rcvr_type, name)); return false; } if (!(fi = func_lookup(impl_tbl, name))) { if (rcvr_type == obj_disabler) { *res = disabler_id; return true; } interp_error(wk, name_node, "%s not found", func_name_str(have_rcvr, rcvr_type, name)); return false; } } if (fi->fuzz_unsafe && disable_fuzz_unsafe_functions) { interp_error(wk, name_node, "%s is disabled", func_name_str(have_rcvr, rcvr_type, name)); return false; } if (have_rcvr && fi->rcvr_transform) { rcvr_id = fi->rcvr_transform(wk, rcvr_id); } TracyCZoneC(tctx_func, 0xff5000, true); #ifdef TRACY_ENABLE const char *func_name = func_name_str(have_rcvr, rcvr_type, name); TracyCZoneName(tctx_func, func_name, strlen(func_name)); #endif bool func_res = fi->func(wk, rcvr_id, args_node, res); TracyCZoneEnd(tctx_func); if (!func_res) { if (disabler_among_args) { *res = disabler_id; disabler_among_args = false; return true; } else { interp_error(wk, name_node, "in %s", func_name_str(have_rcvr, rcvr_type, name)); return false; } } return true; } bool analyze_function(struct workspace *wk, const struct func_impl_name *fi, uint32_t args_node, obj rcvr, obj *res, bool *was_pure) { struct analyze_function_opts old_opts = analyze_function_opts; *res = 0; bool pure = fi->pure; if (rcvr && obj_tainted_by_typeinfo(wk, rcvr)) { pure = false; } if (!rcvr && strcmp(fi->name, "set_variable") == 0) { analyze_function_opts.set_variable_special = true; } analyze_function_opts.do_analyze = true; // pure_function can be set to false even if it was true in the case // that any of its arguments are of type obj_typeinfo analyze_function_opts.pure_function = pure; analyze_function_opts.encountered_error = true; bool func_ret = fi->func(wk, rcvr, args_node, res); pure = analyze_function_opts.pure_function; bool ok = !analyze_function_opts.encountered_error; analyze_function_opts = old_opts; *was_pure = pure; if (pure) { return func_ret; } else { return ok; } } static int32_t function_sig_sort(const void *a, const void *b, void *_ctx) { const struct function_signature *sa = a, *sb = b; if ((sa->is_method && sb->is_method) || (!sa->is_method && !sb->is_method)) { return strcmp(sa->name, sb->name); } else if (sa->is_method) { return 1; } else { return -1; } } void dump_function_signatures(struct workspace *wk) { analyze_function_opts.dump_signature = true; darr_init(&function_sig_dump.sigs, 64, sizeof(struct function_signature)); struct function_signature *sig, empty = { 0 }; uint32_t i; for (i = 0; kernel_func_tbl[wk->lang_mode][i].name; ++i) { sig = darr_get(&function_sig_dump.sigs, darr_push(&function_sig_dump.sigs, &empty)); sig->impl = &kernel_func_tbl[wk->lang_mode][i]; sig->name = kernel_func_tbl[wk->lang_mode][i].name; sig->returns = typechecking_type_to_s(wk, kernel_func_tbl[wk->lang_mode][i].return_type); kernel_func_tbl[wk->lang_mode][i].func(wk, 0, 0, 0); } { enum obj_type t; for (t = 0; t < obj_type_count; ++t) { if (!func_tbl[t][wk->lang_mode]) { continue; } for (i = 0; func_tbl[t][wk->lang_mode][i].name; ++i) { sig = darr_get(&function_sig_dump.sigs, darr_push(&function_sig_dump.sigs, &empty)); sig->impl = &func_tbl[t][wk->lang_mode][i]; sig->is_method = true; sig->name = get_cstr(wk, make_strf(wk, "%s.%s", obj_type_to_s(t), func_tbl[t][wk->lang_mode][i].name)); sig->returns = typechecking_type_to_s(wk, func_tbl[t][wk->lang_mode][i].return_type); func_tbl[t][wk->lang_mode][i].func(wk, 0, 0, 0); } } } for (i = 0; i < module_count; ++i) { if (!module_func_tbl[i][wk->lang_mode]) { continue; } uint32_t j; for (j = 0; module_func_tbl[i][wk->lang_mode][j].name; ++j) { sig = darr_get(&function_sig_dump.sigs, darr_push(&function_sig_dump.sigs, &empty)); sig->impl = &module_func_tbl[i][wk->lang_mode][j]; sig->is_method = true; sig->name = get_cstr(wk, make_strf(wk, "import('%s').%s", module_names[i], module_func_tbl[i][wk->lang_mode][j].name)); sig->returns = typechecking_type_to_s(wk, module_func_tbl[i][wk->lang_mode][j].return_type); module_func_tbl[i][wk->lang_mode][j].func(wk, 0, 0, 0); } } darr_sort(&function_sig_dump.sigs, NULL, function_sig_sort); for (i = 0; i < function_sig_dump.sigs.len; ++i) { sig = darr_get(&function_sig_dump.sigs, i); if (sig->impl->extension) { printf("extension:"); } printf("%s\n", sig->name); if (sig->posargs) { printf(" posargs:\n%s", sig->posargs); } if (sig->varargs) { printf(" varargs:\n%s", sig->varargs); } if (sig->optargs) { printf(" optargs:\n%s", sig->optargs); } if (sig->kwargs) { printf(" kwargs:\n%s", sig->kwargs); } printf(" returns:\n %s\n", sig->returns); } darr_destroy(&function_sig_dump.sigs); } muon-0.2.0/src/functions/compiler.c000066400000000000000000001607411441402162300172520ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Luke Drummond * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "args.h" #include "backend/common_args.h" #include "coerce.h" #include "compilers.h" #include "error.h" #include "functions/common.h" #include "functions/compiler.h" #include "functions/kernel/custom_target.h" #include "functions/kernel/dependency.h" #include "lang/interpreter.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "sha_256.h" enum compile_mode { compile_mode_preprocess, compile_mode_compile, compile_mode_link, compile_mode_run, }; struct compiler_check_opts { struct run_cmd_ctx cmd_ctx; enum compile_mode mode; obj comp_id; struct args_kw *deps, *inc, *required; obj args; bool skip_run_check; bool src_is_path; const char *output_path; bool from_cache; obj cache_key, cache_val; }; static const char * bool_to_yn(bool v) { return v ? "\033[32mYES\033[0m" : "\033[31mNO\033[0m"; } MUON_ATTR_FORMAT(printf, 3, 4) static void compiler_log(struct workspace *wk, obj compiler, const char *fmt, ...) { va_list args; va_start(args, fmt); struct obj_compiler *comp = get_obj_compiler(wk, compiler); LLOG_I("%s compiler: ", compiler_language_to_s(comp->lang)); log_plainv(fmt, args); log_plain("\n"); va_end(args); } MUON_ATTR_FORMAT(printf, 3, 4) static void compiler_check_log(struct workspace *wk, struct compiler_check_opts *opts, const char *fmt, ...) { va_list args; va_start(args, fmt); struct obj_compiler *comp = get_obj_compiler(wk, opts->comp_id); LLOG_I("%s compiler: ", compiler_language_to_s(comp->lang)); log_plainv(fmt, args); if (opts->from_cache) { log_plain(" \033[36mcached\033[0m"); } log_plain("\n"); va_end(args); } static void add_extra_compiler_check_args(struct workspace *wk, struct obj_compiler *comp, obj args) { if (comp->lang == compiler_language_cpp) { // From meson: // -fpermissive allows non-conforming code to compile which is necessary // for many C++ checks. Particularly, the has_header_symbol check is // too strict without this and always fails. obj_array_push(wk, args, make_str(wk, "-fpermissive")); } } static bool add_include_directory_args(struct workspace *wk, struct args_kw *inc, struct build_dep *dep, obj comp_id, obj compiler_args) { obj include_dirs; make_obj(wk, &include_dirs, obj_array); if (inc && inc->set) { obj includes; if (!coerce_include_dirs(wk, inc->node, inc->val, false, &includes)) { return false; } obj_array_extend_nodup(wk, include_dirs, includes); } if (dep) { obj_array_extend_nodup(wk, include_dirs, dep->include_directories); } setup_compiler_args_includes(wk, comp_id, include_dirs, compiler_args, false); return true; } static bool compiler_check_cache(struct workspace *wk, struct obj_compiler *comp, const char *argstr, uint32_t argc, const char *src, uint8_t sha_res[32], bool *res, obj *res_val) { uint32_t argstr_len; { uint32_t i = 0; const char *p = argstr; for (;; ++p) { if (!p[0]) { if (++i >= argc) { break; } } } argstr_len = p - argstr; } enum { sha_idx_argstr = 0, sha_idx_ver = sha_idx_argstr + 32, sha_idx_src = sha_idx_ver + 32, sha_len = sha_idx_src + 32 }; uint8_t sha[sha_len] = { 0 }; calc_sha_256(&sha[sha_idx_argstr], argstr, argstr_len); if (comp->ver) { const struct str *ver = get_str(wk, comp->ver); calc_sha_256(&sha[sha_idx_ver], ver->s, ver->len); } calc_sha_256(&sha[sha_idx_src], src, strlen(src)); calc_sha_256(sha_res, sha, sha_len); /* LLOG_I("sha: "); */ /* uint32_t i; */ /* for (i = 0; i < 32; ++i) { */ /* log_plain("%02x", sha_res[i]); */ /* } */ /* log_plain("\n"); */ obj arr; if (obj_dict_index_strn(wk, wk->compiler_check_cache, (const char *)sha_res, 32, &arr)) { obj cache_res; obj_array_index(wk, arr, 0, &cache_res); *res = get_obj_bool(wk, cache_res); obj_array_index(wk, arr, 1, res_val); return true; } else { return false; } } static void set_compiler_cache(struct workspace *wk, obj key, bool res, obj val) { if (!key) { return; } obj arr, cache_res; if (obj_dict_index(wk, wk->compiler_check_cache, key, &arr)) { obj_array_index(wk, arr, 0, &cache_res); set_obj_bool(wk, cache_res, res); obj_array_set(wk, arr, 1, val); } else { make_obj(wk, &arr, obj_array); make_obj(wk, &cache_res, obj_bool); set_obj_bool(wk, cache_res, res); obj_array_push(wk, arr, cache_res); obj_array_push(wk, arr, val); obj_dict_set(wk, wk->compiler_check_cache, key, arr); } } static bool compiler_check(struct workspace *wk, struct compiler_check_opts *opts, const char *src, uint32_t err_node, bool *res) { enum requirement_type req = requirement_auto; if (opts->required && opts->required->set) { if (!coerce_requirement(wk, opts->required, &req)) { return false; } } if (req == requirement_skip) { *res = false; return true; } struct obj_compiler *comp = get_obj_compiler(wk, opts->comp_id); enum compiler_type t = comp->type; obj compiler_args; make_obj(wk, &compiler_args, obj_array); obj_array_extend(wk, compiler_args, comp->cmd_arr); get_std_args(wk, current_project(wk), NULL, compiler_args, comp->lang, t); add_extra_compiler_check_args(wk, comp, compiler_args); switch (opts->mode) { case compile_mode_run: case compile_mode_link: get_option_link_args(wk, current_project(wk), NULL, compiler_args, comp->lang); /* fallthrough */ case compile_mode_compile: get_option_compile_args(wk, current_project(wk), NULL, compiler_args, comp->lang); /* fallthrough */ case compile_mode_preprocess: break; } bool have_dep = false; struct build_dep dep = { 0 }; if (opts->deps && opts->deps->set) { have_dep = true; dep_process_deps(wk, opts->deps->val, &dep); obj_array_extend_nodup(wk, compiler_args, dep.compile_args); } if (!add_include_directory_args(wk, opts->inc, have_dep ? &dep : NULL, opts->comp_id, compiler_args)) { return false; } switch (opts->mode) { case compile_mode_preprocess: push_args(wk, compiler_args, compilers[t].args.preprocess_only()); break; case compile_mode_compile: push_args(wk, compiler_args, compilers[t].args.compile_only()); break; case compile_mode_run: break; case compile_mode_link: push_args(wk, compiler_args, linkers[compilers[t].linker].args.fatal_warnings()); break; } obj source_path; if (opts->src_is_path) { source_path = make_str(wk, src); } else { SBUF(test_source_path); path_join(wk, &test_source_path, wk->muon_private, "test."); sbuf_pushs(wk, &test_source_path, compiler_language_extension(comp->lang)); source_path = sbuf_into_str(wk, &test_source_path); } obj_array_push(wk, compiler_args, source_path); SBUF(test_output_path); const char *output_path; if (opts->output_path) { output_path = opts->output_path; } else if (opts->mode == compile_mode_run) { path_join(wk, &test_output_path, wk->muon_private, "compiler_check_exe"); output_path = test_output_path.buf; } else { path_join(wk, &test_output_path, wk->muon_private, "test.o"); output_path = test_output_path.buf; } push_args(wk, compiler_args, compilers[t].args.output(output_path)); if (have_dep) { struct setup_linker_args_ctx sctx = { .linker = compilers[t].linker, .link_lang = comp->lang, .args = &dep }; setup_linker_args(wk, NULL, NULL, &sctx); obj_array_extend_nodup(wk, compiler_args, dep.link_args); } if (opts->args) { obj_array_extend(wk, compiler_args, opts->args); } bool ret = false; struct run_cmd_ctx cmd_ctx = { 0 }; const char *argstr; uint32_t argc; join_args_argstr(wk, &argstr, &argc, compiler_args); uint8_t sha[32]; if (compiler_check_cache(wk, comp, argstr, argc, src, sha, res, &opts->cache_val)) { opts->from_cache = true; return true; } opts->cache_key = make_strn(wk, (const char *)sha, 32); if (!opts->src_is_path) { if (!fs_write(get_cstr(wk, source_path), (const uint8_t *)src, strlen(src))) { return false; } } L("compiling: '%s'", get_cstr(wk, source_path)); if (!run_cmd(&cmd_ctx, argstr, argc, NULL, 0)) { interp_error(wk, err_node, "error: %s", cmd_ctx.err_msg); goto ret; } L("compiler stdout: '%s'", cmd_ctx.err.buf); L("compiler stderr: '%s'", cmd_ctx.out.buf); if (opts->mode == compile_mode_run) { if (cmd_ctx.status != 0) { if (opts->skip_run_check) { *res = false; ret = true; goto ret; } else { LOG_W("failed to compile test, rerun with -v to see compiler invocation"); goto ret; } } if (!run_cmd_argv(&opts->cmd_ctx, (char *const []){ (char *)output_path, NULL }, NULL, 0)) { LOG_W("compiled binary failed to run: %s", opts->cmd_ctx.err_msg); run_cmd_ctx_destroy(&opts->cmd_ctx); goto ret; } else if (!opts->skip_run_check && opts->cmd_ctx.status != 0) { LOG_W("compiled binary returned an error (exit code %d)", opts->cmd_ctx.status); run_cmd_ctx_destroy(&opts->cmd_ctx); goto ret; } *res = true; } else { *res = cmd_ctx.status == 0; } set_compiler_cache(wk, opts->cache_key, *res, 0); ret = true; ret: run_cmd_ctx_destroy(&cmd_ctx); if (!*res && req == requirement_required) { assert(opts->required); interp_error(wk, opts->required->node, "a required compiler check failed"); return false; } return ret; } static int64_t compiler_check_parse_output_int(struct compiler_check_opts *opts) { char *endptr; int64_t size; size = strtol(opts->cmd_ctx.out.buf, &endptr, 10); if (*endptr) { LOG_W("compiler check binary had malformed output '%s'", opts->cmd_ctx.out.buf); return -1; } return size; } enum cc_kwargs { cc_kw_args, cc_kw_dependencies, cc_kw_prefix, cc_kw_required, cc_kw_include_directories, cc_kw_name, cc_kw_guess, cc_kw_high, cc_kw_low, cc_kwargs_count, cm_kw_args = 1 << 0, cm_kw_dependencies = 1 << 1, cm_kw_prefix = 1 << 2, cm_kw_required = 1 << 3, cm_kw_include_directories = 1 << 4, cm_kw_name = 1 << 5, cm_kw_guess = 1 << 6, cm_kw_high = 1 << 7, cm_kw_low = 1 << 8, }; static void compiler_opts_init(obj rcvr, struct args_kw *akw, struct compiler_check_opts *opts) { opts->comp_id = rcvr; if (akw[cc_kw_dependencies].set) { opts->deps = &akw[cc_kw_dependencies]; } if (akw[cc_kw_args].set) { opts->args = akw[cc_kw_args].val; } if (akw[cc_kw_include_directories].set) { opts->inc = &akw[cc_kw_include_directories]; } if (akw[cc_kw_required].set) { opts->required = &akw[cc_kw_required]; } } static bool func_compiler_check_args_common(struct workspace *wk, obj rcvr, uint32_t args_node, struct args_norm *an, struct args_kw **kw_res, struct compiler_check_opts *opts, enum cc_kwargs args_mask) { static struct args_kw akw[cc_kwargs_count + 1] = { 0 }; struct args_kw akw_base[] = { [cc_kw_args] = { "args", ARG_TYPE_ARRAY_OF | obj_string }, [cc_kw_dependencies] = { "dependencies", ARG_TYPE_ARRAY_OF | tc_dependency }, [cc_kw_prefix] = { "prefix", obj_string }, [cc_kw_required] = { "required", tc_required_kw }, [cc_kw_include_directories] = { "include_directories", ARG_TYPE_ARRAY_OF | tc_coercible_inc }, [cc_kw_name] = { "name", obj_string }, [cc_kw_guess] = { "guess", obj_number, }, [cc_kw_high] = { "high", obj_number, }, [cc_kw_low] = { "low", obj_number, }, 0 }; memcpy(akw, akw_base, sizeof(struct args_kw) * cc_kwargs_count); struct args_kw *use_akw; if (kw_res && args_mask) { *kw_res = akw; use_akw = akw; } else { use_akw = NULL; } if (!interp_args(wk, args_node, an, NULL, use_akw)) { return false; } if (use_akw) { uint32_t i; for (i = 0; i < cc_kwargs_count; ++i) { if ((args_mask & (1 << i))) { continue; } else if (akw[i].set) { interp_error(wk, akw[i].node, "invalid keyword '%s'", akw[i].key); return false; } } } compiler_opts_init(rcvr, akw, opts); return true; } static const char * compiler_check_prefix(struct workspace *wk, struct args_kw *akw) { if (akw[cc_kw_prefix].set) { return get_cstr(wk, akw[cc_kw_prefix].val); } else { return ""; } } static bool func_compiler_sizeof(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_run, .skip_run_check = true, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "%s\n" "int main(void) { printf(\"%%ld\", (long)(sizeof(%s))); return 0; }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val) ); bool ok; if (compiler_check(wk, &opts, src, an[0].node, &ok) && ok) { if (!opts.from_cache) { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); } } else { if (!opts.from_cache) { make_obj(wk, res, obj_number); set_obj_number(wk, *res, -1); } } if (opts.from_cache) { *res = opts.cache_val; } else { run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *res); } compiler_check_log(wk, &opts, "sizeof %s: %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res) ); return true; } static bool func_compiler_alignment(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_run, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "#include \n" "%s\n" "struct tmp { char c; %s target; };\n" "int main(void) { printf(\"%%d\", (int)(offsetof(struct tmp, target))); return 0; }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val) ); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok) || !ok) { return false; } if (opts.from_cache) { *res = opts.cache_val; } else { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *res); } compiler_check_log(wk, &opts, "alignment of %s: %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res) ); return true; } static bool func_compiler_compute_int(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_run, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories | cm_kw_guess | cm_kw_high | cm_kw_low)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "#include \n" "%s\n" "int main(void) {\n" "int d = (%s);\n" "printf(\"%%d\", d);\n" "}\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val) ); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok) || !ok) { return false; } if (opts.from_cache) { *res = opts.cache_val; } else { make_obj(wk, res, obj_number); set_obj_number(wk, *res, compiler_check_parse_output_int(&opts)); run_cmd_ctx_destroy(&opts.cmd_ctx); set_compiler_cache(wk, opts.cache_key, true, *res); } compiler_check_log(wk, &opts, "%s computed to %" PRId64, get_cstr(wk, an[0].val), get_obj_number(wk, *res) ); return true; } static bool get_has_function_attribute_test(const struct str *name, const char **res) { /* These functions are based on the following code: * https://git.savannah.gnu.org/gitweb/?p=autoconf-archive.git;a=blob_plain;f=m4/ax_gcc_func_attribute.m4, * which is licensed under the following terms: * * Copyright (c) 2013 Gabriele Svelto * * Copying and distribution of this file, with or without modification, are * permitted in any medium without royalty provided the copyright notice * and this notice are preserved. This file is offered as-is, without any * warranty. */ struct { const char *name, *src; } tests[] = { { "alias", "#ifdef __cplusplus\n" "extern \"C\" {\n" "#endif\n" "int foo(void) { return 0; }\n" "int bar(void) __attribute__((alias(\"foo\")));\n" "#ifdef __cplusplus\n" "}\n" "#endif\n" }, { "aligned", "int foo(void) __attribute__((aligned(32)));\n" }, { "alloc_size", "void *foo(int a) __attribute__((alloc_size(1)));\n" }, { "always_inline", "inline __attribute__((always_inline)) int foo(void) { return 0; }\n" }, { "artificial", "inline __attribute__((artificial)) int foo(void) { return 0; }\n" }, { "cold", "int foo(void) __attribute__((cold));\n" }, { "const", "int foo(void) __attribute__((const));\n" }, { "constructor", "int foo(void) __attribute__((constructor));\n" }, { "constructor_priority", "int foo( void ) __attribute__((__constructor__(65535/2)));\n" }, { "deprecated", "int foo(void) __attribute__((deprecated(\"\")));\n" }, { "destructor", "int foo(void) __attribute__((destructor));\n" }, { "dllexport", "__declspec(dllexport) int foo(void) { return 0; }\n" }, { "dllimport", "__declspec(dllimport) int foo(void);\n" }, { "error", "int foo(void) __attribute__((error(\"\")));\n" }, { "externally_visible", "int foo(void) __attribute__((externally_visible));\n" }, { "fallthrough", "int foo( void ) {\n" " switch (0) {\n" " case 1: __attribute__((fallthrough));\n" " case 2: break;\n" " }\n" " return 0;\n" "};\n" }, { "flatten", "int foo(void) __attribute__((flatten));\n" }, { "format", "int foo(const char * p, ...) __attribute__((format(printf, 1, 2)));\n" }, { "format_arg", "char * foo(const char * p) __attribute__((format_arg(1)));\n" }, { "force_align_arg_pointer", "__attribute__((force_align_arg_pointer)) int foo(void) { return 0; }\n" }, { "gnu_inline", "inline __attribute__((gnu_inline)) int foo(void) { return 0; }\n" }, { "hot", "int foo(void) __attribute__((hot));\n" }, { "ifunc", "('int my_foo(void) { return 0; }'\n" " static int (*resolve_foo(void))(void) { return my_foo; }'\n" " int foo(void) __attribute__((ifunc(\"resolve_foo\")));'),\n" }, { "leaf", "__attribute__((leaf)) int foo(void) { return 0; }\n" }, { "malloc", "int *foo(void) __attribute__((malloc));\n" }, { "noclone", "int foo(void) __attribute__((noclone));\n" }, { "noinline", "__attribute__((noinline)) int foo(void) { return 0; }\n" }, { "nonnull", "int foo(char * p) __attribute__((nonnull(1)));\n" }, { "noreturn", "int foo(void) __attribute__((noreturn));\n" }, { "nothrow", "int foo(void) __attribute__((nothrow));\n" }, { "optimize", "__attribute__((optimize(3))) int foo(void) { return 0; }\n" }, { "packed", "struct __attribute__((packed)) foo { int bar; };\n" }, { "pure", "int foo(void) __attribute__((pure));\n" }, { "returns_nonnull", "int *foo(void) __attribute__((returns_nonnull));\n" }, { "section", "#if defined(__APPLE__) && defined(__MACH__)\n" " extern int foo __attribute__((section(\"__BAR,__bar\")));\n" "#else\n" " extern int foo __attribute__((section(\".bar\")));\n" "#endif\n" }, { "sentinel", "int foo(const char *bar, ...) __attribute__((sentinel));" }, { "unused", "int foo(void) __attribute__((unused));\n" }, { "used", "int foo(void) __attribute__((used));\n" }, { "visibility", "int foo_def(void) __attribute__((visibility(\"default\")));\n" "int foo_hid(void) __attribute__((visibility(\"hidden\")));\n" "int foo_int(void) __attribute__((visibility(\"internal\")));\n" }, { "visibility:default", "int foo(void) __attribute__((visibility(\"default\")));\n" }, { "visibility:hidden", "int foo(void) __attribute__((visibility(\"hidden\")));\n" }, { "visibility:internal", "int foo(void) __attribute__((visibility(\"internal\")));\n" }, { "visibility:protected", "int foo(void) __attribute__((visibility(\"protected\")));\n" }, { "warning", "int foo(void) __attribute__((warning(\"\")));\n" }, { "warn_unused_result", "int foo(void) __attribute__((warn_unused_result));\n" }, { "weak", "int foo(void) __attribute__((weak));\n" }, { "weakref", "static int foo(void) { return 0; }\n" "static int var(void) __attribute__((weakref(\"foo\")));\n" }, { 0 } }; uint32_t i; for (i = 0; tests[i].name; ++i) { if (str_eql(name, &WKSTR(tests[i].name))) { *res = tests[i].src; return true; } } return false; } static bool compiler_has_function_attribute(struct workspace *wk, obj comp_id, uint32_t err_node, obj arg, bool *has_fattr) { struct compiler_check_opts opts = { .mode = compile_mode_compile, .comp_id = comp_id, }; const char *src; if (!get_has_function_attribute_test(get_str(wk, arg), &src)) { interp_error(wk, err_node, "unknown attribute '%s'", get_cstr(wk, arg)); return false; } if (!compiler_check(wk, &opts, src, err_node, has_fattr)) { return false; } compiler_check_log(wk, &opts, "has attribute %s: %s", get_cstr(wk, arg), bool_to_yn(*has_fattr) ); return true; } static bool func_compiler_has_function_attribute(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } bool has_fattr; if (!compiler_has_function_attribute(wk, rcvr, an[0].node, an[0].val, &has_fattr)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, has_fattr); return true; } struct func_compiler_get_supported_function_attributes_iter_ctx { uint32_t node; obj arr, compiler; }; static enum iteration_result func_compiler_get_supported_function_attributes_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_function_attributes_iter_ctx *ctx = _ctx; bool has_fattr; if (!compiler_has_function_attribute(wk, ctx->compiler, ctx->node, val_id, &has_fattr)) { return ir_err; } if (has_fattr) { obj_array_push(wk, ctx->arr, val_id); } return ir_cont; } static bool func_compiler_get_supported_function_attributes(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_function_attributes_iter_ctx) { .compiler = rcvr, .arr = *res, .node = an[0].node, }, func_compiler_get_supported_function_attributes_iter); } static bool func_compiler_has_function(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_link, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } const char *prefix = compiler_check_prefix(wk, akw), *func = get_cstr(wk, an[0].val); bool prefix_contains_include = strstr(prefix, "#include") != NULL; char src[BUF_SIZE_4k]; if (prefix_contains_include) { snprintf(src, BUF_SIZE_4k, "%s\n" "#include \n" "#if defined __stub_%s || defined __stub___%s\n" "fail fail fail this function is not going to work\n" "#endif\n" "int main(void) {\n" "void *a = (void*) &%s;\n" "long long b = (long long) a;\n" "return (int) b;\n" "}\n", prefix, func, func, func ); } else { snprintf(src, BUF_SIZE_4k, "#define %s muon_disable_define_of_%s\n" "%s\n" "#include \n" "#undef %s\n" "#ifdef __cplusplus\n" "extern \"C\"\n" "#endif\n" "char %s (void);\n" "#if defined __stub_%s || defined __stub___%s\n" "fail fail fail this function is not going to work\n" "#endif\n" "int main(void) { return %s(); }\n", func, func, prefix, func, func, func, func, func ); } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } if (!ok) { bool is_builtin = str_startswith(get_str(wk, an[0].val), &WKSTR("__builtin_")); const char *__builtin_ = is_builtin ? "" : "__builtin_"; /* With some toolchains (MSYS2/mingw for example) the compiler * provides various builtins which are not really implemented and * fall back to the stdlib where they aren't provided and fail at * build/link time. In case the user provides a header, including * the header didn't lead to the function being defined, and the * function we are checking isn't a builtin itself we assume the * builtin is not functional and we just error out. */ snprintf(src, BUF_SIZE_4k, "%s\n" "int main(void) {\n" "#if !%d && !defined(%s) && !%d\n" " #error \"No definition for %s%s found in the prefix\"\n" "#endif\n" "#ifdef __has_builtin\n" " #if !__has_builtin(%s%s)\n" " #error \"%s%s not found\"\n" " #endif\n" "#elif ! defined(%s)\n" " %s%s;\n" "#endif\n" "return 0;\n" "}\n", prefix, !prefix_contains_include, func, is_builtin, __builtin_, func, __builtin_, func, __builtin_, func, func, __builtin_, func ); if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); compiler_check_log(wk, &opts, "has function %s: %s", get_cstr(wk, an[0].val), bool_to_yn(ok) ); return true; } static bool compiler_has_header_symbol_c(struct workspace *wk, uint32_t node, struct compiler_check_opts *opts, const char *prefix, obj header, obj symbol, bool *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "int main(void) {\n" " /* If it's not defined as a macro, try to use as a symbol */\n" " #ifndef %s\n" " %s;\n" " #endif\n" " return 0;\n" "}\n", prefix, get_cstr(wk, header), get_cstr(wk, symbol), get_cstr(wk, symbol) ); if (!compiler_check(wk, opts, src, node, res)) { return false; } return true; } static bool compiler_has_header_symbol_cpp(struct workspace *wk, uint32_t node, struct compiler_check_opts *opts, const char *prefix, obj header, obj symbol, bool *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "using %s;\n" "int main(void) {\n" " return 0;\n" "}\n", prefix, get_cstr(wk, header), get_cstr(wk, symbol) ); if (!compiler_check(wk, opts, src, node, res)) { return false; } return true; } static bool func_compiler_has_header_symbol(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_compile, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_required | cm_kw_include_directories)) { return false; } bool ok; switch (get_obj_compiler(wk, rcvr)->lang) { case compiler_language_c: if (!compiler_has_header_symbol_c(wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } break; case compiler_language_cpp: if (!compiler_has_header_symbol_c(wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } if (!ok) { if (!compiler_has_header_symbol_cpp(wk, an[0].node, &opts, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } } break; default: UNREACHABLE; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); compiler_check_log(wk, &opts, "header %s has symbol %s: %s", get_cstr(wk, an[0].val), get_cstr(wk, an[1].val), bool_to_yn(ok) ); return true; } static bool compiler_get_define(struct workspace *wk, uint32_t err_node, struct compiler_check_opts *opts, const char *prefix, const char *def, obj *res) { SBUF(output_path); path_join(wk, &output_path, wk->muon_private, "get_define_output"); opts->output_path = output_path.buf; opts->mode = compile_mode_preprocess; char src[BUF_SIZE_4k]; const char *delim = "MUON_GET_DEFINE_DELIMITER\n"; const uint32_t delim_len = strlen(delim); snprintf(src, BUF_SIZE_4k, "%s\n" "#ifndef %s\n" "#define %s\n" "#endif \n" "%s%s\n", prefix, def, def, delim, def ); struct source output = { 0 }; bool ok; if (!compiler_check(wk, opts, src, err_node, &ok)) { return false; } else if (!ok) { goto failed; } if (opts->from_cache) { *res = opts->cache_val; goto done; } if (!fs_read_entire_file(output_path.buf, &output)) { return false; } *res = make_str(wk, ""); bool started = false; bool in_quotes = false; bool esc = false; bool joining = false; uint32_t i; for (i = 0; i < output.len; ++i) { if (!started && strncmp(&output.src[i], delim, delim_len) == 0) { i += delim_len; started = true; if (i >= output.len) { break; } } if (!started) { continue; } switch (output.src[i]) { case '"': if (esc) { esc = false; } else { in_quotes = !in_quotes; if (!in_quotes || joining) { uint32_t start = i; ++i; for (; i < output.len; ++i) { if (!strchr("\t ", output.src[i])) { break; } } if (output.src[i] == '"') { joining = true; ++i; } else { i = start; } } } break; case '\\': esc = true; break; } if (output.src[i] == '\n') { break; } if (started) { str_appn(wk, *res, &output.src[i], 1); } } fs_source_destroy(&output); set_compiler_cache(wk, opts->cache_key, true, *res); done: compiler_check_log(wk, opts, "defines %s as '%s'", def, get_cstr(wk, *res)); return true; failed: fs_source_destroy(&output); interp_error(wk, err_node, "failed to get define: '%s'", def); return false; } static bool func_compiler_get_define(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } if (!compiler_get_define(wk, an[0].node, &opts, compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val), res)) { return false; } return true; } static bool func_compiler_symbols_have_underscore_prefix(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct compiler_check_opts opts = { .comp_id = rcvr }; if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } obj pre; if (!compiler_get_define(wk, args_node, &opts, "", "__USER_LABEL_PREFIX__", &pre)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, str_eql(get_str(wk, pre), &WKSTR("_"))); return true; } static bool func_compiler_check_common(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res, enum compile_mode mode) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = mode, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_name | cm_kw_include_directories)) { return false; } enum obj_type t = get_obj_type(wk, an[0].val); const char *src; switch (t) { case obj_string: src = get_cstr(wk, an[0].val); break; case obj_file: { src = get_file_path(wk, an[0].val); opts.src_is_path = true; break; } default: interp_error(wk, an[0].node, "expected file or string, got %s", obj_type_to_s(t)); return false; } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); if (akw[cc_kw_name].set) { const char *mode_s = NULL; switch (mode) { case compile_mode_run: mode_s = "runs"; break; case compile_mode_link: mode_s = "links"; break; case compile_mode_compile: mode_s = "compiles"; break; case compile_mode_preprocess: mode_s = "preprocesses"; break; } compiler_check_log(wk, &opts, "%s %s: %s", get_cstr(wk, akw[cc_kw_name].val), mode_s, bool_to_yn(ok) ); } return true; } static bool func_compiler_compiles(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_compiler_check_common(wk, rcvr, args_node, res, compile_mode_compile); } static bool func_compiler_links(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_compiler_check_common(wk, rcvr, args_node, res, compile_mode_link); } static bool compiler_check_header(struct workspace *wk, uint32_t err_node, struct compiler_check_opts *opts, const char *prefix, const char *hdr, obj *res) { char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "#include <%s>\n" "int main(void) {}\n", prefix, hdr ); bool ok; if (!compiler_check(wk, opts, src, err_node, &ok)) { return false; } const char *mode_s = NULL; switch (opts->mode) { case compile_mode_compile: mode_s = "is usable"; break; case compile_mode_preprocess: mode_s = "found"; break; default: abort(); } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); compiler_check_log(wk, opts, "header %s %s: %s", hdr, mode_s, bool_to_yn(ok) ); return true; } static bool compiler_check_header_common(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res, enum compile_mode mode) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = mode, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_required | cm_kw_include_directories)) { return false; } return compiler_check_header(wk, an[0].node, &opts, compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val), res); } static bool func_compiler_has_header(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_check_header_common(wk, rcvr, args_node, res, compile_mode_preprocess); } static bool func_compiler_check_header(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_check_header_common(wk, rcvr, args_node, res, compile_mode_compile); } static bool func_compiler_has_type(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_compile, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "void bar(void) { sizeof(%s); }\n", compiler_check_prefix(wk, akw), get_cstr(wk, an[0].val) ); bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); compiler_check_log(wk, &opts, "has type %s: %s", get_cstr(wk, an[0].val), bool_to_yn(ok) ); return true; } static bool compiler_has_member(struct workspace *wk, struct compiler_check_opts *opts, uint32_t err_node, const char *prefix, obj target, obj member, bool *res) { opts->mode = compile_mode_compile; char src[BUF_SIZE_4k]; snprintf(src, BUF_SIZE_4k, "%s\n" "void bar(void) {\n" "%s foo;\n" "foo.%s;\n" "}\n", prefix, get_cstr(wk, target), get_cstr(wk, member) ); if (!compiler_check(wk, opts, src, err_node, res)) { return false; } compiler_check_log(wk, opts, "struct %s has member %s: %s", get_cstr(wk, target), get_cstr(wk, member), bool_to_yn(*res) ); return true; } static bool func_compiler_has_member(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } bool ok; if (!compiler_has_member(wk, &opts, an[0].node, compiler_check_prefix(wk, akw), an[0].val, an[1].val, &ok)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ok); return true; } struct compiler_has_members_ctx { struct compiler_check_opts *opts; uint32_t node; const char *prefix; obj target; bool ok; }; static enum iteration_result compiler_has_members_iter(struct workspace *wk, void *_ctx, obj val) { struct compiler_has_members_ctx *ctx = _ctx; if (!typecheck(wk, ctx->node, val, obj_string)) { return ir_err; } bool ok; if (!compiler_has_member(wk, ctx->opts, ctx->node, ctx->prefix, ctx->target, val, &ok)) { return ir_err; } if (!ok) { ctx->ok = false; return ir_done; } return ir_cont; } static bool func_compiler_has_members(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { 0 }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_prefix | cm_kw_include_directories)) { return false; } if (!get_obj_array(wk, an[1].val)->len) { interp_error(wk, an[1].node, "missing member arguments"); return false; } struct compiler_has_members_ctx ctx = { .opts = &opts, .node = an[0].node, .prefix = compiler_check_prefix(wk, akw), .target = an[0].val, .ok = true, }; if (!obj_array_foreach_flat(wk, an[1].val, &ctx, compiler_has_members_iter)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ctx.ok); return true; } static bool func_compiler_run(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; struct args_kw *akw; struct compiler_check_opts opts = { .mode = compile_mode_run, .skip_run_check = true, }; if (!func_compiler_check_args_common(wk, rcvr, args_node, an, &akw, &opts, cm_kw_args | cm_kw_dependencies | cm_kw_name)) { return false; } obj o; if (!obj_array_flatten_one(wk, an[0].val, &o)) { interp_error(wk, an[0].node, "could not flatten argument"); } enum obj_type t = get_obj_type(wk, an[0].val); const char *src; switch (t) { case obj_string: src = get_cstr(wk, an[0].val); break; case obj_file: { src = get_file_path(wk, an[0].val); opts.src_is_path = true; break; } default: interp_error(wk, an[0].node, "expected file or string, got %s", obj_type_to_s(t)); return false; } bool ok; if (!compiler_check(wk, &opts, src, an[0].node, &ok)) { return false; } if (akw[cc_kw_name].set) { compiler_check_log(wk, &opts, "runs %s: %s", get_cstr(wk, akw[cc_kw_name].val), bool_to_yn(ok) ); } make_obj(wk, res, obj_run_result); struct obj_run_result *rr = get_obj_run_result(wk, *res); rr->flags |= run_result_flag_from_compile; if (ok) { rr->flags |= run_result_flag_compile_ok; rr->out = make_strn(wk, opts.cmd_ctx.out.buf, opts.cmd_ctx.out.len); rr->err = make_strn(wk, opts.cmd_ctx.err.buf, opts.cmd_ctx.err.len); rr->status = opts.cmd_ctx.status; } run_cmd_ctx_destroy(&opts.cmd_ctx); return true; } static bool compiler_has_argument(struct workspace *wk, obj comp_id, uint32_t err_node, obj arg, bool *has_argument, enum compile_mode mode) { struct obj_compiler *comp = get_obj_compiler(wk, comp_id); enum compiler_type t = comp->type; obj args; make_obj(wk, &args, obj_array); if (get_obj_type(wk, arg) == obj_string) { obj_array_push(wk, args, arg); } else { obj_array_extend(wk, args, arg); obj str; obj_array_join(wk, true, arg, make_str(wk, " "), &str); arg = str; } push_args(wk, args, compilers[t].args.werror()); struct compiler_check_opts opts = { .mode = mode, .comp_id = comp_id, .args = args, }; const char *src = "int main(void){}\n"; if (!compiler_check(wk, &opts, src, err_node, has_argument)) { return false; } compiler_check_log(wk, &opts, "supports argument '%s': %s", get_cstr(wk, arg), bool_to_yn(*has_argument) ); return true; } struct func_compiler_get_supported_arguments_iter_ctx { uint32_t node; obj arr, compiler; enum compile_mode mode; }; static enum iteration_result func_compiler_get_supported_arguments_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_arguments_iter_ctx *ctx = _ctx; bool has_argument; if (!compiler_has_argument(wk, ctx->compiler, ctx->node, val_id, &has_argument, ctx->mode)) { return false; } if (has_argument) { obj_array_push(wk, ctx->arr, val_id); } return ir_cont; } static bool compiler_has_argument_common(struct workspace *wk, obj rcvr, uint32_t args_node, type_tag glob, obj *res, enum compile_mode mode) { struct args_norm an[] = { { glob | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } bool has_argument; if (!compiler_has_argument(wk, rcvr, an[0].node, an[0].val, &has_argument, mode)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, has_argument); return true; } static bool func_compiler_has_argument(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_has_argument_common(wk, rcvr, args_node, 0, res, compile_mode_compile); } static bool func_compiler_has_link_argument(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_has_argument_common(wk, rcvr, args_node, 0, res, compile_mode_link); } static bool func_compiler_has_multi_arguments(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_has_argument_common(wk, rcvr, args_node, ARG_TYPE_GLOB, res, compile_mode_compile); } static bool func_compiler_has_multi_link_arguments(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_has_argument_common(wk, rcvr, args_node, ARG_TYPE_GLOB, res, compile_mode_link); } static bool compiler_get_supported_arguments(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res, enum compile_mode mode) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_arguments_iter_ctx) { .compiler = rcvr, .arr = *res, .node = an[0].node, .mode = mode, }, func_compiler_get_supported_arguments_iter); } static bool func_compiler_get_supported_arguments(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_get_supported_arguments(wk, rcvr, args_node, res, compile_mode_compile); } static bool func_compiler_get_supported_link_arguments(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_get_supported_arguments(wk, rcvr, args_node, res, compile_mode_link); } static enum iteration_result func_compiler_first_supported_argument_iter(struct workspace *wk, void *_ctx, obj val_id) { struct func_compiler_get_supported_arguments_iter_ctx *ctx = _ctx; bool has_argument; if (!compiler_has_argument(wk, ctx->compiler, ctx->node, val_id, &has_argument, ctx->mode)) { return false; } if (has_argument) { compiler_log(wk, ctx->compiler, "first supported argument: '%s'", get_cstr(wk, val_id)); obj_array_push(wk, ctx->arr, val_id); return ir_done; } return ir_cont; } static bool compiler_first_supported_argument(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res, enum compile_mode mode) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_array); return obj_array_foreach_flat(wk, an[0].val, &(struct func_compiler_get_supported_arguments_iter_ctx) { .compiler = rcvr, .arr = *res, .node = an[0].node, .mode = mode, }, func_compiler_first_supported_argument_iter); } static bool func_compiler_first_supported_argument(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_first_supported_argument(wk, rcvr, args_node, res, compile_mode_compile); } static bool func_compiler_first_supported_link_argument(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return compiler_first_supported_argument(wk, rcvr, args_node, res, compile_mode_link); } static bool func_compiler_get_id(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, compiler_type_to_s(get_obj_compiler(wk, rcvr)->type)); return true; } static bool func_compiler_get_linker_id(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } enum compiler_type t = get_obj_compiler(wk, rcvr)->type; *res = make_str(wk, linker_type_to_s(compilers[t].linker)); return true; } static bool func_compiler_get_argument_syntax(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } const char *syntax; enum compiler_type type = get_obj_compiler(wk, rcvr)->type; switch (type) { case compiler_posix: case compiler_gcc: case compiler_clang: case compiler_apple_clang: syntax = "gcc"; break; default: syntax = "other"; break; } *res = make_str(wk, syntax); return true; } struct compiler_find_library_ctx { struct sbuf *path; obj lib_name; bool only_static; bool found; }; static enum iteration_result compiler_find_library_iter(struct workspace *wk, void *_ctx, obj libdir) { struct compiler_find_library_ctx *ctx = _ctx; SBUF(lib); static const char *pref[] = { "", "lib", NULL }; const char *suf[] = { ".so", ".a", NULL }; if (ctx->only_static) { suf[0] = ".a"; suf[1] = NULL; } uint32_t i, j; for (i = 0; suf[i]; ++i) { for (j = 0; pref[j]; ++j) { sbuf_clear(&lib); sbuf_pushf(wk, &lib, "%s%s%s", pref[j], get_cstr(wk, ctx->lib_name), suf[i]); path_join(wk, ctx->path, get_cstr(wk, libdir), lib.buf); if (fs_file_exists(ctx->path->buf)) { ctx->found = true; return ir_done; } } } return ir_cont; } struct compiler_find_library_check_headers_ctx { uint32_t err_node; struct compiler_check_opts *opts; const char *prefix; bool ok; }; static enum iteration_result compiler_find_library_check_headers_iter(struct workspace *wk, void *_ctx, obj hdr) { struct compiler_find_library_check_headers_ctx *ctx = _ctx; obj res; if (!compiler_check_header(wk, ctx->err_node, ctx->opts, ctx->prefix, get_cstr(wk, hdr), &res)) { return ir_err; } ctx->ok &= get_obj_bool(wk, res); return ir_cont; } static bool func_compiler_find_library(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_static, kw_disabler, kw_dirs, // has_headers kw_has_headers, kw_header_required, kw_header_args, kw_header_dependencies, kw_header_include_directories, kw_header_no_builtin_args, // TODO kw_header_prefix, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_static] = { "static", obj_bool }, [kw_disabler] = { "disabler", obj_bool }, [kw_dirs] = { "dirs", ARG_TYPE_ARRAY_OF | obj_string }, // has_headers [kw_has_headers] = { "has_headers", ARG_TYPE_ARRAY_OF | obj_string }, [kw_header_required] = { "header_required", }, [kw_header_args] = { "header_args", }, [kw_header_dependencies] = { "header_dependencies", }, [kw_header_include_directories] = { "header_include_directories", ARG_TYPE_ARRAY_OF | tc_coercible_inc }, [kw_header_no_builtin_args] = { "header_no_builtin_args", }, [kw_header_prefix] = { "header_prefix", }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!akw[kw_has_headers].set) { uint32_t i; for (i = kw_header_required; i <= kw_header_prefix; ++i) { if (akw[i].set) { interp_error(wk, akw[i].node, "header_ keywords are invalid without " "also specifying the has_headers keyword"); return false; } } } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); dep->type = dependency_type_external_library; if (requirement == requirement_skip) { return true; } SBUF(library_path); struct compiler_find_library_ctx ctx = { .path = &library_path, .lib_name = an[0].val, .only_static = akw[kw_static].set ? get_obj_bool(wk, akw[kw_static].val) : false, }; struct obj_compiler *comp = get_obj_compiler(wk, rcvr); bool found_from_dirs_kw = false; if (akw[kw_dirs].set) { if (!obj_array_foreach(wk, akw[kw_dirs].val, &ctx, compiler_find_library_iter)) { return false; } if (ctx.found) { found_from_dirs_kw = true; } } if (!ctx.found) { if (!obj_array_foreach(wk, comp->libdirs, &ctx, compiler_find_library_iter)) { return false; } } if (ctx.found && akw[kw_has_headers].set) { struct args_kw header_kwargs[cc_kwargs_count + 1] = { [cc_kw_args] = akw[kw_header_args], [cc_kw_dependencies] = akw[kw_header_dependencies], [cc_kw_prefix] = akw[kw_header_prefix], [cc_kw_required] = akw[kw_header_required], [cc_kw_include_directories] = akw[kw_header_include_directories], }; struct compiler_check_opts header_check_opts = { 0 }; compiler_opts_init(rcvr, header_kwargs, &header_check_opts); struct compiler_find_library_check_headers_ctx check_headers_ctx = { .ok = true, .err_node = akw[kw_has_headers].node, .opts = &header_check_opts, .prefix = compiler_check_prefix(wk, header_kwargs), }; obj_array_foreach(wk, akw[kw_has_headers].val, &check_headers_ctx, compiler_find_library_check_headers_iter); if (!check_headers_ctx.ok) { ctx.found = false; } } if (!ctx.found) { if (requirement == requirement_required) { interp_error(wk, an[0].node, "library not found"); return false; } LOG_W("library '%s' not found", get_cstr(wk, an[0].val)); if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = disabler_id; } return true; } compiler_log(wk, rcvr, "found library '%s' at '%s'", get_cstr(wk, an[0].val), ctx.path->buf); dep->flags |= dep_flag_found; make_obj(wk, &dep->dep.link_with, obj_array); obj path_str = make_str(wk, ctx.path->buf); obj_array_push(wk, dep->dep.link_with, path_str); if (found_from_dirs_kw) { make_obj(wk, &dep->dep.rpath, obj_array); obj_array_push(wk, dep->dep.rpath, path_str); } dep->dep.link_language = comp->lang; return true; } struct compiler_preprocess_create_tgt_ctx { obj output; obj cmd; obj res; uint32_t input_node, output_node; enum compiler_type t; const char *output_dir; }; static enum iteration_result compiler_preprocess_create_tgt_iter(struct workspace *wk, void *_ctx, obj val) { struct compiler_preprocess_create_tgt_ctx *ctx = _ctx; obj cmd; obj_array_dup(wk, ctx->cmd, &cmd); push_args(wk, cmd, compilers[ctx->t].args.output("@OUTPUT@")); obj_array_push(wk, cmd, make_str(wk, "@INPUT@")); struct make_custom_target_opts opts = { .input_node = ctx->input_node, .output_node = ctx->output_node, .command_node = 0, .input_orig = val, .output_orig = ctx->output, .output_dir = ctx->output_dir, .command_orig = cmd, .extra_args_valid = true, }; obj tgt; if (!make_custom_target(wk, &opts, &tgt)) { return ir_err; } struct obj_custom_target *t = get_obj_custom_target(wk, tgt); obj output; if (!obj_array_flatten_one(wk, t->output, &output)) { UNREACHABLE; } t->name = make_strf(wk, "", get_file_path(wk, output)); obj_array_push(wk, current_project(wk)->targets, tgt); obj_array_push(wk, ctx->res, output); return ir_cont; } static bool func_compiler_preprocess(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_string | tc_file | tc_custom_target | tc_generated_list }, ARG_TYPE_NULL }; enum kwargs { kw_compile_args, kw_include_directories, kw_output, }; struct args_kw akw[] = { [kw_compile_args] = { "compile_args", ARG_TYPE_ARRAY_OF | tc_string }, [kw_include_directories] = { "include_directories", ARG_TYPE_ARRAY_OF | tc_coercible_inc }, [kw_output] = { "output", tc_string, .required = true }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct obj_compiler *comp = get_obj_compiler(wk, rcvr); obj cmd; obj_array_dup(wk, comp->cmd_arr, &cmd); push_args(wk, cmd, compilers[comp->type].args.preprocess_only()); push_args(wk, cmd, compilers[comp->type].args.specify_lang("assembler-with-cpp")); get_std_args(wk, current_project(wk), NULL, cmd, comp->lang, comp->type); get_option_compile_args(wk, current_project(wk), NULL, cmd, comp->lang); push_args(wk, cmd, compilers[comp->type].args.include("@OUTDIR@")); push_args(wk, cmd, compilers[comp->type].args.include("@CURRENT_SOURCE_DIR@")); if (!add_include_directory_args(wk, &akw[kw_include_directories], NULL, rcvr, cmd)) { return false; } if (akw[kw_compile_args].set) { obj_array_extend(wk, cmd, akw[kw_compile_args].val); } make_obj(wk, res, obj_array); SBUF(output_dir); sbuf_pushs(wk, &output_dir, get_cstr(wk, current_project(wk)->build_dir)); path_push(wk, &output_dir, "preprocess.p"); if (!fs_mkdir_p(output_dir.buf)) { return false; } struct compiler_preprocess_create_tgt_ctx ctx = { .output = akw[kw_output].val, .cmd = cmd, .res = *res, .input_node = an[0].node, .output_node = akw[kw_output].node, .output_dir = output_dir.buf, .t = comp->type }; obj_array_foreach(wk, an[0].val, &ctx, compiler_preprocess_create_tgt_iter); return true; } static bool func_compiler_cmd_array(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_compiler(wk, rcvr)->cmd_arr; return true; } static bool func_compiler_version(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_compiler(wk, rcvr)->ver; return true; } const struct func_impl_name impl_tbl_compiler[] = { { "alignment", func_compiler_alignment, tc_number }, { "check_header", func_compiler_check_header, tc_bool }, { "cmd_array", func_compiler_cmd_array, tc_array }, { "compiles", func_compiler_compiles, tc_bool }, { "compute_int", func_compiler_compute_int, tc_number }, { "find_library", func_compiler_find_library, tc_dependency }, { "first_supported_argument", func_compiler_first_supported_argument, tc_array }, { "first_supported_link_argument", func_compiler_first_supported_link_argument, tc_array }, { "get_argument_syntax", func_compiler_get_argument_syntax, tc_string }, { "get_define", func_compiler_get_define, tc_string }, { "get_id", func_compiler_get_id, tc_string }, { "get_linker_id", func_compiler_get_linker_id, tc_string }, { "get_supported_arguments", func_compiler_get_supported_arguments, tc_array }, { "get_supported_function_attributes", func_compiler_get_supported_function_attributes, tc_array }, { "get_supported_link_arguments", func_compiler_get_supported_link_arguments, tc_array }, { "has_argument", func_compiler_has_argument, tc_bool }, { "has_function", func_compiler_has_function, tc_bool }, { "has_function_attribute", func_compiler_has_function_attribute, tc_bool }, { "has_header", func_compiler_has_header, tc_bool }, { "has_header_symbol", func_compiler_has_header_symbol, tc_bool }, { "has_link_argument", func_compiler_has_link_argument, tc_bool }, { "has_member", func_compiler_has_member, tc_bool }, { "has_members", func_compiler_has_members, tc_bool }, { "has_multi_arguments", func_compiler_has_multi_arguments, tc_bool }, { "has_multi_link_arguments", func_compiler_has_multi_link_arguments, tc_bool }, { "has_type", func_compiler_has_type, tc_bool }, { "links", func_compiler_links, tc_bool }, { "preprocess", func_compiler_preprocess, tc_array }, { "run", func_compiler_run, tc_run_result }, { "sizeof", func_compiler_sizeof, tc_number }, { "symbols_have_underscore_prefix", func_compiler_symbols_have_underscore_prefix, tc_bool }, { "version", func_compiler_version, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/configuration_data.c000066400000000000000000000122211441402162300212650ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/common.h" #include "functions/configuration_data.h" #include "lang/interpreter.h" #include "log.h" static bool func_configuration_data_set_quoted(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_description, // TODO }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj dict = get_obj_configuration_data(wk, rcvr)->dict; const char *s = get_cstr(wk, an[1].val); obj str = make_str(wk, "\""); for (; *s; ++s) { if (*s == '"') { str_app(wk, str, "\\"); } str_appn(wk, str, s, 1); } str_app(wk, str, "\""); obj_dict_set(wk, dict, an[0].val, str); return true; } static bool func_configuration_data_set(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { tc_string | tc_number | tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_description, // ingnored }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj dict = get_obj_configuration_data(wk, rcvr)->dict; obj_dict_set(wk, dict, an[0].val, an[1].val); return true; } static bool func_configuration_data_set10(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_bool }, ARG_TYPE_NULL }; enum kwargs { kw_description, // ignored }; struct args_kw akw[] = { [kw_description] = { "description", obj_string, }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj dict = get_obj_configuration_data(wk, rcvr)->dict; obj n; make_obj(wk, &n, obj_number); set_obj_number(wk, n, get_obj_bool(wk, an[1].val) ? 1 : 0); obj_dict_set(wk, dict, an[0].val, n); return true; } static bool configuration_data_get(struct workspace *wk, uint32_t err_node, obj conf, obj key, obj def, obj *res) { obj dict = get_obj_configuration_data(wk, conf)->dict; if (!obj_dict_index(wk, dict, key, res)) { if (def) { *res = def; } else { interp_error(wk, err_node, "key '%s' not found", get_cstr(wk, key)); return false; } } return true; } static bool func_configuration_data_get(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } return configuration_data_get(wk, an[0].node, rcvr, an[0].val, ao[0].val, res); } static bool func_configuration_data_get_unquoted(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } obj v; if (!configuration_data_get(wk, an[0].node, rcvr, an[0].val, ao[0].val, &v)) { return false; } const char *s = get_cstr(wk, v); uint32_t l = strlen(s); if (l >= 2 && s[0] == '"' && s[l - 1] == '"') { *res = make_strn(wk, &s[1], l - 2); } else { *res = v; } return true; } static enum iteration_result obj_dict_keys_iter(struct workspace *wk, void *_ctx, obj k, obj _v) { obj *res = _ctx; obj_array_push(wk, *res, k); return ir_cont; } static bool func_configuration_data_keys(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } obj dict = get_obj_configuration_data(wk, rcvr)->dict; make_obj(wk, res, obj_array); obj_dict_foreach(wk, dict, res, obj_dict_keys_iter); return true; } static bool func_configuration_data_has(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj _, dict = get_obj_configuration_data(wk, rcvr)->dict; make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, obj_dict_index(wk, dict, an[0].val, &_)); return true; } static bool func_configuration_data_merge_from(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_configuration_data }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj_dict_merge_nodup(wk, get_obj_configuration_data(wk, rcvr)->dict, get_obj_configuration_data(wk, an[0].val)->dict ); return true; } const struct func_impl_name impl_tbl_configuration_data[] = { { "get", func_configuration_data_get, tc_any }, { "get_unquoted", func_configuration_data_get_unquoted, tc_any }, { "has", func_configuration_data_has, tc_bool }, { "keys", func_configuration_data_keys, tc_array }, { "merge_from", func_configuration_data_merge_from }, { "set", func_configuration_data_set }, { "set10", func_configuration_data_set10 }, { "set_quoted", func_configuration_data_set_quoted }, { NULL, NULL }, }; muon-0.2.0/src/functions/custom_target.c000066400000000000000000000026101441402162300203060ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/custom_target.h" #include "functions/file.h" #include "lang/interpreter.h" #include "log.h" bool custom_target_is_linkable(struct workspace *wk, obj ct) { struct obj_custom_target *tgt = get_obj_custom_target(wk, ct); if (get_obj_array(wk, tgt->output)->len == 1) { obj out; obj_array_index(wk, tgt->output, 0, &out); return file_is_linkable(wk, out); } return false; } static bool func_custom_target_to_list(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_custom_target(wk, rcvr)->output; return true; } static bool func_custom_target_full_path(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } obj elem; if (!obj_array_flatten_one(wk, get_obj_custom_target(wk, rcvr)->output, &elem)) { interp_error(wk, args_node, "this custom_target has multiple outputs"); return false; } *res = *get_obj_file(wk, elem); return true; } const struct func_impl_name impl_tbl_custom_target[] = { { "full_path", func_custom_target_full_path, tc_string }, { "to_list", func_custom_target_to_list, tc_array }, { NULL, NULL }, }; muon-0.2.0/src/functions/dependency.c000066400000000000000000000202431441402162300175460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: Harley Swick * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "coerce.h" #include "external/libpkgconf.h" #include "functions/build_target.h" #include "functions/common.h" #include "functions/dependency.h" #include "functions/kernel/dependency.h" #include "lang/interpreter.h" #include "log.h" #include "platform/path.h" static bool func_dependency_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (get_obj_dependency(wk, rcvr)->flags & dep_flag_found) == dep_flag_found); return true; } static bool dep_get_pkgconfig_variable(struct workspace *wk, obj dep, uint32_t node, obj var, obj *res) { struct obj_dependency *d = get_obj_dependency(wk, dep); if (d->type != dependency_type_pkgconf) { interp_error(wk, node, "dependency not from pkgconf"); return false; } if (!muon_pkgconf_get_variable(wk, get_cstr(wk, d->name), get_cstr(wk, var), res)) { return false; } return true; } static bool func_dependency_get_pkgconfig_variable(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_default, }; struct args_kw akw[] = { [kw_default] = { "default", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!dep_get_pkgconfig_variable(wk, rcvr, an[0].node, an[0].val, res)) { if (akw[kw_default].set) { *res = akw[kw_default].val; } else { interp_error(wk, an[0].node, "undefined pkg_config variable"); return false; } } return true; } static bool func_dependency_get_variable(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_pkgconfig, kw_internal, kw_default_value, }; struct args_kw akw[] = { [kw_pkgconfig] = { "pkgconfig", obj_string }, [kw_internal] = { "internal", obj_string }, [kw_default_value] = { "default_value", obj_string }, 0 }; if (!interp_args(wk, args_node, NULL, ao, akw)) { return false; } uint32_t node = args_node; if (ao[0].set) { node = ao[0].node; if (!akw[kw_pkgconfig].set) { akw[kw_pkgconfig].set = true; akw[kw_pkgconfig].node = ao[0].node; akw[kw_pkgconfig].val = ao[0].val; } if (!akw[kw_internal].set) { akw[kw_internal].set = true; akw[kw_internal].node = ao[0].node; akw[kw_internal].val = ao[0].val; } } struct obj_dependency *dep = get_obj_dependency(wk, rcvr); if (dep->type == dependency_type_pkgconf) { if (akw[kw_pkgconfig].set) { node = akw[kw_pkgconfig].node; if (dep_get_pkgconfig_variable(wk, rcvr, akw[kw_pkgconfig].node, akw[kw_pkgconfig].val, res)) { return true; } } } else if (dep->variables) { if (akw[kw_internal].set) { node = akw[kw_internal].node; if (obj_dict_index(wk, dep->variables, akw[kw_internal].val, res)) { return true; } } } if (akw[kw_default_value].set) { *res = akw[kw_default_value].val; return true; } else { interp_error(wk, node, "pkgconfig file has no such variable"); return false; } } static bool func_dependency_version(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } obj version = get_obj_dependency(wk, rcvr)->version; if (version) { *res = version; } else { *res = make_str(wk, "unknown"); } return true; } static bool func_dependency_type_name(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } struct obj_dependency *dep = get_obj_dependency(wk, rcvr); if (!(dep->flags & dep_flag_found)) { *res = make_str(wk, "not-found"); return true; } const char *n = NULL; switch (dep->type) { case dependency_type_pkgconf: n = "pkgconfig"; break; case dependency_type_declared: n = "internal"; break; case dependency_type_appleframeworks: case dependency_type_threads: n = "system"; break; case dependency_type_external_library: n = "library"; break; } *res = make_str(wk, n); return true; } static bool func_dependency_name(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } struct obj_dependency *dep = get_obj_dependency(wk, rcvr); if (dep->type == dependency_type_declared) { *res = make_str(wk, "internal"); } else { *res = dep->name; } return true; } static bool func_dependency_partial_dependency(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { enum kwargs { kw_compile_args, kw_includes, kw_link_args, kw_links, kw_sources, }; struct args_kw akw[] = { [kw_compile_args] = { "compile_args", obj_bool }, [kw_includes] = { "includes", obj_bool }, [kw_link_args] = { "link_args", obj_bool }, [kw_links] = { "links", obj_bool }, [kw_sources] = { "sources", obj_bool }, 0 }; if (!interp_args(wk, args_node, NULL, NULL, akw)) { return false; } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, rcvr), *partial = get_obj_dependency(wk, *res); *partial = *dep; partial->dep = (struct build_dep){ 0 }; if (akw[kw_compile_args].set && get_obj_bool(wk, akw[kw_compile_args].val)) { partial->dep.compile_args = dep->dep.compile_args; } if (akw[kw_includes].set && get_obj_bool(wk, akw[kw_includes].val)) { partial->dep.include_directories = dep->dep.include_directories; } if (akw[kw_link_args].set && get_obj_bool(wk, akw[kw_link_args].val)) { partial->dep.link_args = dep->dep.link_args; } if (akw[kw_links].set && get_obj_bool(wk, akw[kw_links].val)) { partial->dep.link_with = dep->dep.link_with; partial->dep.link_whole = dep->dep.link_whole; partial->dep.link_with_not_found = dep->dep.link_with_not_found; partial->dep.raw.link_with = dep->dep.raw.link_with; partial->dep.raw.link_whole = dep->dep.raw.link_whole; } if (akw[kw_sources].set && get_obj_bool(wk, akw[kw_sources].val)) { partial->dep.sources = dep->dep.sources; } return true; } static bool func_dependency_as_system(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } enum include_type inc_type = include_type_system; if (ao[0].set) { if (!coerce_include_type(wk, get_str(wk, ao[0].val), ao[0].node, &inc_type)) { return false; } } make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); *dep = *get_obj_dependency(wk, rcvr); obj old_includes = dep->dep.include_directories; make_obj(wk, &dep->dep.include_directories, obj_array); dep_process_includes(wk, old_includes, inc_type, dep->dep.include_directories); dep->include_type = inc_type; return true; } static bool func_dependency_include_type(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } const char *s = NULL; switch (get_obj_dependency(wk, rcvr)->include_type) { case include_type_preserve: s = "preserve"; break; case include_type_system: s = "system"; break; case include_type_non_system: s = "non-system"; break; default: assert(false && "unreachable"); break; } *res = make_str(wk, s); return true; } const struct func_impl_name impl_tbl_dependency[] = { { "as_system", func_dependency_as_system, tc_dependency }, { "found", func_dependency_found, tc_bool }, { "get_pkgconfig_variable", func_dependency_get_pkgconfig_variable, tc_string }, { "get_variable", func_dependency_get_variable, tc_string }, { "include_type", func_dependency_include_type, tc_string }, { "partial_dependency", func_dependency_partial_dependency, tc_dependency }, { "type_name", func_dependency_type_name, tc_string }, { "name", func_dependency_name, tc_string }, { "version", func_dependency_version, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/dict.c000066400000000000000000000032551441402162300163570ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/dict.h" #include "lang/interpreter.h" #include "log.h" static enum iteration_result dict_keys_iter(struct workspace *wk, void *_ctx, obj k, obj v) { obj *arr = _ctx; obj_array_push(wk, *arr, k); return ir_cont; } static bool func_dict_keys(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_array); obj_dict_foreach(wk, rcvr, res, dict_keys_iter); return true; } static bool func_dict_has_key(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, obj_dict_in(wk, rcvr, an[0].val)); return true; } static bool func_dict_get(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } if (!obj_dict_index(wk, rcvr, an[0].val, res)) { if (ao[0].set) { *res = ao[0].val; } else { interp_error(wk, an[0].node, "key not in dictionary: '%s'", get_cstr(wk, an[0].val)); return false; } } return true; } const struct func_impl_name impl_tbl_dict[] = { { "keys", func_dict_keys, tc_array, true }, { "has_key", func_dict_has_key, tc_bool, true }, { "get", func_dict_get, tc_any, true }, { NULL, NULL }, }; muon-0.2.0/src/functions/disabler.c000066400000000000000000000011361441402162300172150ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/disabler.h" #include "lang/interpreter.h" #include "log.h" static bool func_disabler_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } const struct func_impl_name impl_tbl_disabler[] = { { "found", func_disabler_found, tc_bool }, { NULL, NULL }, }; muon-0.2.0/src/functions/environment.c000066400000000000000000000110421441402162300177710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "functions/common.h" #include "functions/environment.h" #include "lang/interpreter.h" #include "log.h" #include "platform/path.h" static enum iteration_result evironment_to_dict_iter(struct workspace *wk, void *_ctx, obj action) { obj env = *(obj *)_ctx, mode_num, key, val, sep; obj_array_index(wk, action, 0, &mode_num); obj_array_index(wk, action, 1, &key); obj_array_index(wk, action, 2, &val); obj_array_index(wk, action, 3, &sep); enum environment_set_mode mode = get_obj_number(wk, mode_num); if (mode == environment_set_mode_set) { obj_dict_set(wk, env, key, val); return ir_cont; } const char *oval; obj v; if (obj_dict_index(wk, env, key, &v)) { oval = get_cstr(wk, v); } else { if (!(oval = getenv(get_cstr(wk, key)))) { obj_dict_set(wk, env, key, val); return ir_cont; } } obj str; switch (mode) { case environment_set_mode_append: str = make_strf(wk, "%s%s%s", oval, get_cstr(wk, sep), get_cstr(wk, val)); break; case environment_set_mode_prepend: str = make_strf(wk, "%s%s%s", get_cstr(wk, val), get_cstr(wk, sep), oval); break; default: UNREACHABLE; } obj_dict_set(wk, env, key, str); return ir_cont; } bool environment_to_dict(struct workspace *wk, obj env, obj *res) { if (get_obj_type(wk, env) == obj_dict) { *res = env; return true; } make_obj(wk, res, obj_dict); return obj_array_foreach(wk, get_obj_environment(wk, env)->actions, res, evironment_to_dict_iter); } static void environment_or_dict_set(struct workspace *wk, obj env, const char *key, const char *val) { switch (get_obj_type(wk, env)) { case obj_dict: obj_dict_set(wk, env, make_str(wk, key), make_str(wk, val)); break; case obj_environment: environment_set(wk, env, environment_set_mode_set, make_str(wk, key), make_str(wk, val), 0); break; default: UNREACHABLE; } } void set_default_environment_vars(struct workspace *wk, obj env, bool set_subdir) { if (wk->argv0) { // argv0 may not be set, e.g. during `muon install` environment_or_dict_set(wk, env, "MUON_PATH", wk->argv0); } environment_or_dict_set(wk, env, "MESON_BUILD_ROOT", wk->build_root); environment_or_dict_set(wk, env, "MESON_SOURCE_ROOT", wk->source_root); if (set_subdir) { SBUF(subdir); path_relative_to(wk, &subdir, wk->source_root, get_cstr(wk, current_project(wk)->cwd)); environment_or_dict_set(wk, env, "MESON_SUBDIR", subdir.buf); } } bool environment_set(struct workspace *wk, obj env, enum environment_set_mode mode, obj key, obj vals, obj sep) { if (!sep) { sep = make_str(wk, ENV_PATH_SEP_STR); } obj joined; if (get_obj_type(wk, vals) == obj_string) { joined = vals; } else { if (!obj_array_join(wk, false, vals, sep, &joined)) { return false; } } obj elem, mode_num; make_obj(wk, &mode_num, obj_number); set_obj_number(wk, mode_num, mode); make_obj(wk, &elem, obj_array); obj_array_push(wk, elem, mode_num); obj_array_push(wk, elem, key); obj_array_push(wk, elem, joined); obj_array_push(wk, elem, sep); obj_array_push(wk, get_obj_environment(wk, env)->actions, elem); return true; } static bool func_environment_set_common(struct workspace *wk, obj rcvr, uint32_t args_node, enum environment_set_mode mode) { struct args_norm an[] = { { obj_string }, { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_separator, }; struct args_kw akw[] = { [kw_separator] = { "separator", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!get_obj_array(wk, an[1].val)->len) { interp_error(wk, an[1].node, "you must pass at least one value"); return false; } return environment_set(wk, rcvr, mode, an[0].val, an[1].val, akw[kw_separator].val); } static bool func_environment_set(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_environment_set_common(wk, rcvr, args_node, environment_set_mode_set); } static bool func_environment_append(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_environment_set_common(wk, rcvr, args_node, environment_set_mode_append); } static bool func_environment_prepend(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_environment_set_common(wk, rcvr, args_node, environment_set_mode_prepend); } const struct func_impl_name impl_tbl_environment[] = { { "set", func_environment_set }, { "append", func_environment_append }, { "prepend", func_environment_prepend }, { NULL, NULL }, }; muon-0.2.0/src/functions/external_program.c000066400000000000000000000035511441402162300210040ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/external_program.h" #include "guess.h" #include "lang/interpreter.h" #include "log.h" #include "platform/run_cmd.h" void find_program_guess_version(struct workspace *wk, const char *path, obj *ver) { *ver = 0; struct run_cmd_ctx cmd_ctx = { 0 }; if (run_cmd_argv(&cmd_ctx, (char *const []){ (char *)path, "--version", 0 }, NULL, 0) && cmd_ctx.status == 0) { guess_version(wk, cmd_ctx.out.buf, ver); } run_cmd_ctx_destroy(&cmd_ctx); } static bool func_external_program_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, get_obj_external_program(wk, rcvr)->found); return true; } static bool func_external_program_path(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_external_program(wk, rcvr)->full_path; return true; } static bool func_external_program_version(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } struct obj_external_program *prog = get_obj_external_program(wk, rcvr); if (!prog->guessed_ver) { find_program_guess_version(wk, get_cstr(wk, prog->full_path), &prog->ver); prog->guessed_ver = true; } *res = prog->ver; return true; } const struct func_impl_name impl_tbl_external_program[] = { { "found", func_external_program_found, tc_bool }, { "path", func_external_program_path, tc_string }, { "full_path", func_external_program_path, tc_string }, { "version", func_external_program_version, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/feature_opt.c000066400000000000000000000063661441402162300177570ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/feature_opt.h" #include "lang/interpreter.h" #include "log.h" static bool feature_opt_common(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res, enum feature_opt_state state) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, get_obj_feature_opt(wk, rcvr) == state); return true; } static bool func_feature_opt_auto(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return feature_opt_common(wk, rcvr, args_node, res, feature_opt_auto); } static bool func_feature_opt_disabled(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return feature_opt_common(wk, rcvr, args_node, res, feature_opt_disabled); } static bool func_feature_opt_enabled(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return feature_opt_common(wk, rcvr, args_node, res, feature_opt_enabled); } static bool func_feature_opt_allowed(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, rcvr); make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, state == feature_opt_auto || state == feature_opt_enabled); return true; } static bool func_feature_opt_disable_auto_if(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, rcvr); if (!get_obj_bool(wk, an[0].val)) { *res = rcvr; return true; } else if (state == feature_opt_disabled || state == feature_opt_enabled) { *res = rcvr; return true; } else { make_obj(wk, res, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_disabled); return true; } } static bool func_feature_opt_require(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_bool }, ARG_TYPE_NULL }; enum kwargs { kw_error_message, }; struct args_kw akw[] = { [kw_error_message] = { "error_message", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } enum feature_opt_state state = get_obj_feature_opt(wk, rcvr); if (!get_obj_bool(wk, an[0].val)) { if (state == feature_opt_enabled) { interp_error(wk, an[0].node, "%s", akw[kw_error_message].set ? get_cstr(wk, akw[kw_error_message].set) : "requirement not met"); return false; } else { make_obj(wk, res, obj_feature_opt); set_obj_feature_opt(wk, *res, feature_opt_disabled); } } else { *res = rcvr; } return true; } const struct func_impl_name impl_tbl_feature_opt[] = { { "allowed", func_feature_opt_allowed, tc_bool, true }, { "auto", func_feature_opt_auto, tc_bool, true }, { "disable_auto_if", func_feature_opt_disable_auto_if, tc_feature_opt, true }, { "disabled", func_feature_opt_disabled, tc_bool, true }, { "enabled", func_feature_opt_enabled, tc_bool, true }, { "require", func_feature_opt_require, tc_feature_opt, true }, { NULL, NULL }, }; muon-0.2.0/src/functions/file.c000066400000000000000000000016221441402162300163470ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/common.h" #include "functions/file.h" #include "lang/interpreter.h" #include "log.h" bool file_is_linkable(struct workspace *wk, obj file) { const struct str *s = get_str(wk, *get_obj_file(wk, file)); const char *suffs[] = { ".a", ".dll", ".lib", ".so", ".dylib", NULL }; uint32_t i; for (i = 0; suffs[i]; ++i) { if (str_endswith(s, &WKSTR(suffs[i]))) { return true; } } return false; } static bool func_file_full_path(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = *get_obj_file(wk, rcvr); return true; } const struct func_impl_name impl_tbl_file[] = { { "full_path", func_file_full_path, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/generator.c000066400000000000000000000141431441402162300174200ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include // XXX #include "coerce.h" #include "error.h" #include "functions/build_target.h" #include "functions/common.h" #include "functions/generator.h" #include "functions/kernel/custom_target.h" #include "lang/interpreter.h" #include "log.h" #include "platform/path.h" struct generated_list_process_for_target_ctx { uint32_t node; struct obj_generator *g; struct obj_generated_list *gl; const char *dir; bool add_targets, generated_include; obj *res; // for generated_list_process_for_target_result_iter only obj name; obj custom_target, tmp_arr; struct obj_custom_target *t; }; static enum iteration_result generated_list_process_for_target_result_iter(struct workspace *wk, void *_ctx, obj file) { struct generated_list_process_for_target_ctx *ctx = _ctx; obj_array_push(wk, ctx->tmp_arr, file); if (ctx->add_targets) { const char *generated_path = get_cstr(wk, *get_obj_file(wk, file)); enum compiler_language l; if (!ctx->generated_include && filename_to_compiler_language(generated_path, &l) && languages[l].is_header) { ctx->generated_include = true; } SBUF(rel); path_relative_to(wk, &rel, wk->build_root, generated_path); str_app(wk, ctx->name, " "); str_app(wk, ctx->name, rel.buf); } return ir_cont; } static enum iteration_result generated_list_process_for_target_iter(struct workspace *wk, void *_ctx, obj val) { struct generated_list_process_for_target_ctx *ctx = _ctx; SBUF(path); const char *output_dir = ctx->dir; if (ctx->gl->preserve_path_from) { const char *src = get_file_path(wk, val), *base = get_cstr(wk, ctx->gl->preserve_path_from); assert(path_is_subpath(base, src)); SBUF(dir); path_relative_to(wk, &path, base, src); path_dirname(wk, &dir, path.buf); path_join(wk, &path, ctx->dir, dir.buf); output_dir = path.buf; } struct make_custom_target_opts opts = { .input_node = ctx->node, .output_node = ctx->node, .command_node = ctx->node, .input_orig = val, .output_orig = ctx->g->output, .output_dir = output_dir, .build_dir = ctx->dir, .command_orig = ctx->g->raw_command, .depfile_orig = ctx->g->depfile, .capture = ctx->g->capture, .feed = ctx->g->feed, .extra_args = ctx->gl->extra_arguments, .extra_args_valid = true, }; obj tgt; if (!make_custom_target(wk, &opts, &tgt)) { return ir_err; } struct obj_custom_target *t = get_obj_custom_target(wk, tgt); ctx->custom_target = tgt; ctx->t = t; make_obj(wk, &ctx->tmp_arr, obj_array); if (ctx->add_targets) { ctx->name = make_str(wk, ""); } if (!obj_array_foreach(wk, t->output, ctx, generated_list_process_for_target_result_iter)) { return ir_err; } obj_array_extend_nodup(wk, *ctx->res, ctx->tmp_arr); if (ctx->add_targets) { ctx->t->name = make_strf(wk, "", get_cstr(wk, ctx->name)); if (ctx->g->depends) { obj_array_extend(wk, ctx->t->depends, ctx->g->depends); } obj_array_push(wk, current_project(wk)->targets, ctx->custom_target); } return ir_cont; } bool generated_list_process_for_target(struct workspace *wk, uint32_t err_node, obj gl, obj tgt, bool add_targets, obj *res) { struct obj_generated_list *list = get_obj_generated_list(wk, gl); enum obj_type t = get_obj_type(wk, tgt); const char *private_path; switch (t) { case obj_both_libs: tgt = get_obj_both_libs(wk, tgt)->dynamic_lib; /* fallthrough */ case obj_build_target: private_path = get_cstr(wk, get_obj_build_target(wk, tgt)->private_path); break; case obj_custom_target: { private_path = get_cstr(wk, get_obj_custom_target(wk, tgt)->private_path); break; } default: UNREACHABLE; } make_obj(wk, res, obj_array); struct generated_list_process_for_target_ctx ctx = { .node = err_node, .g = get_obj_generator(wk, list->generator), .gl = list, .dir = private_path, .add_targets = add_targets, .res = res, }; if (!obj_array_foreach(wk, list->input, &ctx, generated_list_process_for_target_iter)) { return false; } if (add_targets && t == obj_build_target && ctx.generated_include) { get_obj_build_target(wk, tgt)->flags |= build_tgt_generated_include; } return true; } struct check_preserve_path_from_ctx { const struct obj_generated_list *gl; uint32_t err_node; }; static enum iteration_result check_preserve_path_from_iter(struct workspace *wk, void *_ctx, obj f) { const struct check_preserve_path_from_ctx *ctx = _ctx; const char *src = get_file_path(wk, f), *base = get_cstr(wk, ctx->gl->preserve_path_from); if (!path_is_subpath(base, src)) { interp_error(wk, ctx->err_node, "source file '%s' is not a subdir of preserve_path_from path '%s'", src, base); return ir_err; } return ir_cont; } static bool func_generator_process(struct workspace *wk, obj gen, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_coercible_files }, ARG_TYPE_NULL }; enum kwargs { kw_extra_args, kw_preserve_path_from, }; struct args_kw akw[] = { [kw_extra_args] = { "extra_args", ARG_TYPE_ARRAY_OF | obj_string }, [kw_preserve_path_from] = { "preserve_path_from", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } make_obj(wk, res, obj_generated_list); struct obj_generated_list *gl = get_obj_generated_list(wk, *res); gl->generator = gen; gl->extra_arguments = akw[kw_extra_args].val; gl->preserve_path_from = akw[kw_preserve_path_from].val; if (!coerce_files(wk, an[0].node, an[0].val, &gl->input)) { return false; } if (gl->preserve_path_from) { if (!path_is_absolute(get_cstr(wk, gl->preserve_path_from))) { interp_error(wk, akw[kw_preserve_path_from].node, "preserve_path_from must be an absolute path"); return false; } struct check_preserve_path_from_ctx ctx = { .gl = gl, .err_node = akw[kw_preserve_path_from].node }; if (!obj_array_foreach(wk, gl->input, &ctx, check_preserve_path_from_iter)) { return false; } } return true; } const struct func_impl_name impl_tbl_generator[] = { { "process", func_generator_process, tc_generated_list }, { NULL, NULL }, }; muon-0.2.0/src/functions/kernel.c000066400000000000000000001435441441402162300167220ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: dffdff2423 * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "buf_size.h" #include "coerce.h" #include "error.h" #include "functions/common.h" #include "functions/environment.h" #include "functions/external_program.h" #include "functions/kernel.h" #include "functions/kernel/build_target.h" #include "functions/kernel/configure_file.h" #include "functions/kernel/custom_target.h" #include "functions/kernel/dependency.h" #include "functions/kernel/install.h" #include "functions/kernel/options.h" #include "functions/kernel/subproject.h" #include "functions/modules.h" #include "functions/string.h" #include "lang/interpreter.h" #include "lang/serial.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" #include "wrap.h" static bool project_add_language(struct workspace *wk, uint32_t err_node, obj str, enum requirement_type req, bool *found) { if (req == requirement_skip) { return true; } obj comp_id; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, str), &l)) { if (req == requirement_required) { interp_error(wk, err_node, "%o is not a valid language", str); return false; } else { return true; } } obj res; if (obj_dict_geti(wk, current_project(wk)->compilers, l, &res)) { *found = true; return true; } if (!compiler_detect(wk, &comp_id, l)) { if (req == requirement_required) { interp_error(wk, err_node, "unable to detect %s compiler", get_cstr(wk, str)); return false; } else { return true; } } obj_dict_seti(wk, current_project(wk)->compilers, l, comp_id); /* if we just added a c or cpp compiler, set the assembly compiler to that */ if (l == compiler_language_c || l == compiler_language_cpp) { obj_dict_seti(wk, current_project(wk)->compilers, compiler_language_assembly, comp_id); struct obj_compiler *comp = get_obj_compiler(wk, comp_id); if (comp->type == compiler_clang) { obj llvm_ir_compiler; make_obj(wk, &llvm_ir_compiler, obj_compiler); struct obj_compiler *c = get_obj_compiler(wk, llvm_ir_compiler); *c = *comp; c->type = compiler_clang_llvm_ir; c->lang = compiler_language_llvm_ir; obj_dict_seti(wk, current_project(wk)->compilers, compiler_language_llvm_ir, llvm_ir_compiler); } } *found = true; return true; } struct project_add_language_iter_ctx { uint32_t err_node; enum requirement_type req; bool missing; }; static enum iteration_result project_add_language_iter(struct workspace *wk, void *_ctx, obj val) { struct project_add_language_iter_ctx *ctx = _ctx; bool found = false; if (!project_add_language(wk, ctx->err_node, val, ctx->req, &found)) { return ir_err; } if (!found) { ctx->missing = true; } return ir_cont; } static bool func_project(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { ARG_TYPE_GLOB | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_default_options, kw_license, kw_meson_version, kw_subproject_dir, kw_version, }; struct args_kw akw[] = { [kw_default_options] = { "default_options", ARG_TYPE_ARRAY_OF | obj_string }, [kw_license] = { "license", ARG_TYPE_ARRAY_OF | obj_string }, [kw_meson_version] = { "meson_version", obj_string }, [kw_subproject_dir] = { "subproject_dir", obj_string }, [kw_version] = { "version", tc_string | tc_file }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } current_project(wk)->cfg.name = an[0].val; if (!obj_array_foreach_flat(wk, an[1].val, &(struct project_add_language_iter_ctx) { .err_node = an[1].node, .req = requirement_required, }, project_add_language_iter)) { return false; } current_project(wk)->cfg.license = akw[kw_license].val; if (akw[kw_version].set) { if (get_obj_type(wk, akw[kw_version].val) == obj_string) { current_project(wk)->cfg.version = akw[kw_version].val; } else { struct source ver_src = { 0 }; if (!fs_read_entire_file(get_file_path(wk, akw[kw_version].val), &ver_src)) { interp_error(wk, akw[kw_version].node, "failed to read version file"); return false; } const char *str_ver = ver_src.src; uint32_t i; for (i = 0; ver_src.src[i]; ++i) { if (ver_src.src[i] == '\n') { if (ver_src.src[i + 1]) { interp_error(wk, akw[kw_version].node, "version file is more than one line long"); return false; } break; } } current_project(wk)->cfg.version = make_strn(wk, str_ver, i); fs_source_destroy(&ver_src); } } else { current_project(wk)->cfg.version = make_str(wk, "undefined"); current_project(wk)->cfg.no_version = true; } if (akw[kw_default_options].set) { if (!parse_and_set_default_options(wk, akw[kw_default_options].node, akw[kw_default_options].val, 0, false)) { return false; } } if (wk->cur_project == 0) { if (!prefix_dir_opts(wk)) { return false; } } if (wk->cur_project == 0 && akw[kw_subproject_dir].set) { current_project(wk)->subprojects_dir = akw[kw_subproject_dir].val; } { // subprojects SBUF(subprojects_path); path_join(wk, &subprojects_path, get_cstr(wk, current_project(wk)->source_root), get_cstr(wk, current_project(wk)->subprojects_dir)); if (!wrap_load_all_provides(wk, subprojects_path.buf)) { LOG_E("failed loading wrap provides"); return false; } } LOG_I("configuring '%s', version: %s", get_cstr(wk, current_project(wk)->cfg.name), get_cstr(wk, current_project(wk)->cfg.version) ); return true; } static obj get_project_argument_array(struct workspace *wk, obj dict, enum compiler_language l) { obj arg_arr; if (!obj_dict_geti(wk, dict, l, &arg_arr)) { make_obj(wk, &arg_arr, obj_array); obj_dict_seti(wk, dict, l, arg_arr); } return arg_arr; } struct add_arguments_ctx { uint32_t lang_node; uint32_t args_node; obj args_dict; obj args_to_add; obj arg_arr; }; static enum iteration_result add_arguments_language_iter(struct workspace *wk, void *_ctx, obj val_id) { struct add_arguments_ctx *ctx = _ctx; if (!typecheck(wk, ctx->args_node, val_id, obj_string)) { return ir_err; } obj_array_push(wk, ctx->arg_arr, val_id); return ir_cont; } static enum iteration_result add_arguments_iter(struct workspace *wk, void *_ctx, obj val) { struct add_arguments_ctx *ctx = _ctx; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, val), &l)) { interp_error(wk, ctx->lang_node, "unknown language '%s'", get_cstr(wk, val)); return ir_err; } ctx->arg_arr = get_project_argument_array(wk, ctx->args_dict, l); if (!obj_array_foreach_flat(wk, ctx->args_to_add, ctx, add_arguments_language_iter)) { return ir_err; } return ir_cont; } static bool add_arguments_common(struct workspace *wk, uint32_t args_node, obj args_dict, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_language, kw_native, // ignored }; struct args_kw akw[] = { [kw_language] = { "language", ARG_TYPE_ARRAY_OF | obj_string, .required = true }, [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct add_arguments_ctx ctx = { .lang_node = akw[kw_language].node, .args_node = an[0].node, .args_dict = args_dict, .args_to_add = an[0].val, }; return obj_array_foreach(wk, akw[kw_language].val, &ctx, add_arguments_iter); } static bool func_add_project_arguments(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return add_arguments_common(wk, args_node, current_project(wk)->args, res); } static bool func_add_global_arguments(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (wk->cur_project != 0) { interp_error(wk, args_node, "add_global_arguments cannot be called from a subproject"); return false; } return add_arguments_common(wk, args_node, wk->global_args, res); } static bool func_add_project_link_arguments(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return add_arguments_common(wk, args_node, current_project(wk)->link_args, res); } static bool func_add_global_link_arguments(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (wk->cur_project != 0) { interp_error(wk, args_node, "add_global_link_arguments cannot be called from a subproject"); return false; } return add_arguments_common(wk, args_node, wk->global_link_args, res); } struct add_project_dependencies_ctx { uint32_t lang_node; struct build_dep *d; }; static enum iteration_result add_project_dependencies_iter(struct workspace *wk, void *_ctx, obj lang) { struct add_project_dependencies_ctx *ctx = _ctx; enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, lang), &l)) { interp_error(wk, ctx->lang_node, "unknown language '%s'", get_cstr(wk, lang)); return ir_err; } obj res; if (!obj_dict_geti(wk, current_project(wk)->compilers, l, &res)) { // NOTE: Its a little weird that the other add_project_xxx // functions don't check this and this function does, but that // is how meson does it. interp_error(wk, ctx->lang_node, "undeclared language '%s'", get_cstr(wk, lang)); return ir_err; } obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->args, l), ctx->d->compile_args); obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->link_args, l), ctx->d->link_args); obj_array_extend(wk, get_project_argument_array(wk, current_project(wk)->include_dirs, l), ctx->d->include_directories); return ir_cont; } static bool func_add_project_dependencies(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_language, kw_native, // ignored }; struct args_kw akw[] = { [kw_language] = { "language", ARG_TYPE_ARRAY_OF | obj_string, .required = true }, [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct build_dep d = { 0 }; dep_process_deps(wk, an[0].val, &d); struct add_project_dependencies_ctx ctx = { .lang_node = akw[kw_language].node, .d = &d, }; obj_array_foreach(wk, akw[kw_language].val, &ctx, add_project_dependencies_iter); return true; } static bool func_add_languages(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct project_add_language_iter_ctx ctx = { .err_node = an[0].node, }; if (!coerce_requirement(wk, &akw[kw_required], &ctx.req)) { return false; } if (!obj_array_foreach(wk, an[0].val, &ctx, project_add_language_iter)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, !ctx.missing); return true; } static bool func_files(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } return coerce_files(wk, an[0].node, an[0].val, res); } struct find_program_iter_ctx { bool found; uint32_t node, version_node; obj version; obj dirs; obj *res; }; struct find_program_custom_dir_ctx { const char *prog; struct sbuf *buf; bool found; }; static enum iteration_result find_program_custom_dir_iter(struct workspace *wk, void *_ctx, obj val) { struct find_program_custom_dir_ctx *ctx = _ctx; path_join(wk, ctx->buf, get_cstr(wk, val), ctx->prog); if (fs_file_exists(ctx->buf->buf)) { ctx->found = true; return ir_done; } return ir_cont; } static bool find_program_check_override(struct workspace *wk, struct find_program_iter_ctx *ctx, obj prog) { obj override; if (!obj_dict_index(wk, wk->find_program_overrides, prog, &override)) { return true; } obj over = 0, op; switch (get_obj_type(wk, override)) { case obj_array: obj_array_index(wk, override, 0, &op); obj_array_index(wk, override, 1, &over); break; case obj_external_program: op = override; struct obj_external_program *ep = get_obj_external_program(wk, op); if (!ep->found) { return true; } if (ctx->version) { find_program_guess_version(wk, get_cstr(wk, ep->full_path), &over); } break; default: UNREACHABLE; } if (ctx->version && over) { bool comparison_result; if (!version_compare(wk, ctx->version_node, get_str(wk, over), ctx->version, &comparison_result)) { return false; } else if (!comparison_result) { return true; } } if (get_obj_type(wk, op) == obj_file) { obj newres; make_obj(wk, &newres, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, newres); ep->found = true; ep->full_path = *get_obj_file(wk, op); op = newres; } ctx->found = true; *ctx->res = op; return true; } static bool find_program_check_fallback(struct workspace *wk, struct find_program_iter_ctx *ctx, obj prog) { obj fallback_arr, subproj_name; if (obj_dict_index(wk, current_project(wk)->wrap_provides_exes, prog, &fallback_arr)) { obj_array_flatten_one(wk, fallback_arr, &subproj_name); obj subproj; if (!subproject(wk, subproj_name, requirement_auto, NULL, NULL, &subproj) && get_obj_subproject(wk, subproj)->found) { return true; } if (!find_program_check_override(wk, ctx, prog)) { return false; } else if (!ctx->found) { obj _; if (!obj_dict_index(wk, wk->find_program_overrides, prog, &_)) { interp_warning(wk, 0, "subproject %o claims to provide %o, but did not override it", subproj_name, prog); } } } return true; } static bool find_program(struct workspace *wk, struct find_program_iter_ctx *ctx, obj prog) { const char *str; obj ver = 0; bool guessed_ver = false; if (!typecheck(wk, ctx->node, prog, tc_file | tc_string | tc_external_program)) { return false; } enum obj_type t = get_obj_type(wk, prog); switch (t) { case obj_file: str = get_file_path(wk, prog); break; case obj_string: str = get_cstr(wk, prog); break; case obj_external_program: if (get_obj_external_program(wk, prog)->found) { *ctx->res = prog; ctx->found = true; } return true; default: UNREACHABLE_RETURN; } const char *path; SBUF(buf); struct find_program_custom_dir_ctx dir_ctx = { .buf = &buf, .prog = str, }; /* 1. Program overrides set via meson.override_find_program() */ if (t == obj_string) { if (!find_program_check_override(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } /* 2. [provide] sections in subproject wrap files, if wrap_mode is set to forcefallback */ enum wrap_mode wrap_mode = get_option_wrap_mode(wk); if (t == obj_string && wrap_mode == wrap_mode_forcefallback) { if (!find_program_check_fallback(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } /* TODO: 3. [binaries] section in your machine files */ /* 4. Directories provided using the dirs: kwarg */ if (ctx->dirs) { obj_array_foreach(wk, ctx->dirs, &dir_ctx, find_program_custom_dir_iter); if (dir_ctx.found) { path = buf.buf; goto found; } } /* 5. Project's source tree relative to the current subdir */ /* If you use the return value of configure_file(), the current subdir inside the build tree is used instead */ path_join(wk, &buf, get_cstr(wk, current_project(wk)->cwd), str); if (fs_file_exists(buf.buf)) { path = buf.buf; goto found; } /* 6. PATH environment variable */ if (fs_find_cmd(wk, &buf, str)) { path = buf.buf; goto found; } /* 7. [provide] sections in subproject wrap files, if wrap_mode is set to anything other than nofallback */ if (t == obj_string && wrap_mode != wrap_mode_nofallback) { if (!find_program_check_fallback(wk, ctx, prog)) { return false; } if (ctx->found) { return true; } } return true; found: if (ctx->version) { find_program_guess_version(wk, path, &ver); guessed_ver = true; if (!ver) { return true; // no version to check against } bool comparison_result; if (!version_compare(wk, ctx->version_node, get_str(wk, ver), ctx->version, &comparison_result)) { return false; } else if (!comparison_result) { return true; } } make_obj(wk, ctx->res, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *ctx->res); ep->found = true; ep->full_path = make_str(wk, path); ep->guessed_ver = guessed_ver; ep->ver = ver; ctx->found = true; return true; } static enum iteration_result find_program_iter(struct workspace *wk, void *_ctx, obj val) { struct find_program_iter_ctx *ctx = _ctx; if (!find_program(wk, ctx, val)) { return ir_err; } return ctx->found ? ir_done : ir_cont; } static bool func_find_program(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_string | tc_file }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, kw_disabler, kw_dirs, kw_version, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, [kw_disabler] = { "disabler", obj_bool }, [kw_dirs] = { "dirs", ARG_TYPE_ARRAY_OF | obj_string }, [kw_version] = { "version", ARG_TYPE_ARRAY_OF | obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { make_obj(wk, res, obj_external_program); get_obj_external_program(wk, *res)->found = false; return true; } struct find_program_iter_ctx ctx = { .node = an[0].node, .version = akw[kw_version].val, .dirs = akw[kw_dirs].val, .res = res, }; obj_array_foreach_flat(wk, an[0].val, &ctx, find_program_iter); if (!ctx.found) { if (requirement == requirement_required) { interp_error(wk, an[0].node, "program not found"); return false; } if (akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = disabler_id; } else { make_obj(wk, res, obj_external_program); get_obj_external_program(wk, *res)->found = false; } } return true; } static bool func_include_directories(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_coercible_inc }, ARG_TYPE_NULL }; enum kwargs { kw_is_system, }; struct args_kw akw[] = { [kw_is_system] = { "is_system", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } bool is_system = akw[kw_is_system].set ? get_obj_bool(wk, akw[kw_is_system].val) : false; if (!coerce_include_dirs(wk, an[0].node, an[0].val, is_system, res)) { return false; } return true; } static bool func_generator(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_exe }, ARG_TYPE_NULL }; enum kwargs { kw_output, kw_arguments, kw_capture, kw_depfile, kw_depends, }; struct args_kw akw[] = { [kw_output] = { "output", ARG_TYPE_ARRAY_OF | obj_string, .required = true }, [kw_arguments] = { "arguments", obj_array, .required = true }, [kw_capture] = { "capture", obj_bool }, [kw_depfile] = { "depfile", obj_string }, [kw_depends] = { "depends", tc_depends_kw }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj command; make_obj(wk, &command, obj_array); obj_array_push(wk, command, an[0].val); obj_array_extend(wk, command, akw[kw_arguments].val); make_obj(wk, res, obj_generator); struct obj_generator *gen = get_obj_generator(wk, *res); gen->output = akw[kw_output].val; gen->raw_command = command; gen->depfile = akw[kw_depfile].val; gen->capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val); if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } gen->depends = depends; } return true; } static bool func_assert(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_bool }, ARG_TYPE_NULL }; struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } *res = 0; if (!get_obj_bool(wk, an[0].val)) { if (ao[0].set) { LOG_E("%s", get_cstr(wk, ao[0].val)); } return false; } return true; } static enum iteration_result message_print_iter(struct workspace *wk, void *_ctx, obj val) { obj_fprintf(wk, log_file(), "%#o ", val); return ir_cont; } static bool func_debug(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } log_plain(log_clr() ? "\033[35mdebug\033[0m: " : "debug: "); obj_array_foreach(wk, an[0].val, NULL, message_print_iter); log_plain("\n"); *res = 0; return true; } static bool func_message(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } log_plain(log_clr() ? "\033[34mmessage\033[0m " : "message: "); obj_array_foreach(wk, an[0].val, NULL, message_print_iter); log_plain("\n"); *res = 0; return true; } static bool func_error(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } log_plain(log_clr() ? "\033[31merror\033[0m " : "error: "); obj_array_foreach(wk, an[0].val, NULL, message_print_iter); log_plain("\n"); *res = 0; return false; } static bool func_warning(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_message }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } log_plain(log_clr() ? "\033[33mwarn\033[0m " : "warn: "); obj_array_foreach(wk, an[0].val, NULL, message_print_iter); log_plain("\n"); *res = 0; return true; } static bool func_run_command(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_string | tc_file | tc_external_program | tc_compiler }, ARG_TYPE_NULL }; enum kwargs { kw_check, kw_env, kw_capture, }; struct args_kw akw[] = { [kw_check] = { "check", obj_bool }, [kw_env] = { "env", tc_coercible_env }, [kw_capture] = { "capture", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } const char *argstr, *envstr; uint32_t argc, envc; { obj arg0; obj cmd_file = 0; struct find_program_iter_ctx find_program_ctx = { .node = an[0].node, .res = &cmd_file, }; if (!get_obj_array(wk, an[0].val)->len) { interp_error(wk, an[0].node, "missing command"); return false; } obj_array_index(wk, an[0].val, 0, &arg0); if (get_obj_type(wk, arg0) == obj_compiler) { obj cmd_arr = get_obj_compiler(wk, arg0)->cmd_arr; obj tail; obj_array_tail(wk, an[0].val, &tail); obj_array_dup(wk, cmd_arr, &an[0].val); obj_array_extend_nodup(wk, an[0].val, tail); } else { if (!find_program(wk, &find_program_ctx, arg0)) { return false; } else if (!find_program_ctx.found) { interp_error(wk, an[0].node, "unable to find program %o", arg0); return false; } obj_array_set(wk, an[0].val, 0, cmd_file); } obj args; if (!arr_to_args(wk, arr_to_args_external_program, an[0].val, &args)) { return false; } workspace_add_regenerate_deps(wk, args); join_args_argstr(wk, &argstr, &argc, args); } { obj env; if (!coerce_environment_from_kwarg(wk, &akw[kw_env], true, &env)) { return false; } env_to_envstr(wk, &envstr, &envc, env); } bool ret = false; struct run_cmd_ctx cmd_ctx = { 0 }; if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { interp_error(wk, an[0].node, "%s", cmd_ctx.err_msg); goto ret; } if (akw[kw_check].set && get_obj_bool(wk, akw[kw_check].val) && cmd_ctx.status != 0) { interp_error(wk, an[0].node, "command failed: '%s'", cmd_ctx.err.buf); return false; } make_obj(wk, res, obj_run_result); struct obj_run_result *run_result = get_obj_run_result(wk, *res); run_result->status = cmd_ctx.status; if (akw[kw_capture].set && !get_obj_bool(wk, akw[kw_capture].val)) { run_result->out = make_str(wk, ""); run_result->err = make_str(wk, ""); } else { run_result->out = make_strn(wk, cmd_ctx.out.buf, cmd_ctx.out.len); run_result->err = make_strn(wk, cmd_ctx.err.buf, cmd_ctx.err.len); } ret = true; ret: run_cmd_ctx_destroy(&cmd_ctx); return ret; } static bool func_run_target(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_command, kw_depends, kw_env, }; struct args_kw akw[] = { [kw_command] = { "command", tc_command_array, .required = true }, [kw_depends] = { "depends", tc_depends_kw }, [kw_env] = { "env", tc_coercible_env }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct make_custom_target_opts opts = { .name = an[0].val, .command_node = akw[kw_command].node, .command_orig = akw[kw_command].val, }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->flags |= custom_target_console; if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depends); } if (!coerce_environment_from_kwarg(wk, &akw[kw_env], true, &tgt->env)) { return false; } L("adding run target '%s'", get_cstr(wk, tgt->name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } static enum iteration_result subdir_if_found_iter(struct workspace *wk, void *_ctx, obj v) { struct obj_dependency *dep = get_obj_dependency(wk, v); bool *all_found = _ctx; if (!(dep->flags & dep_flag_found)) { *all_found = false; return ir_done; } return ir_cont; } static bool func_subdir(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_if_found, }; struct args_kw akw[] = { [kw_if_found] = { "if_found", ARG_TYPE_ARRAY_OF | tc_dependency }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (akw[kw_if_found].set) { bool all_found = true; obj_array_foreach(wk, akw[kw_if_found].val, &all_found, subdir_if_found_iter); if (!all_found) { return true; } } SBUF(build_dir); obj old_cwd = current_project(wk)->cwd; obj old_build_dir = current_project(wk)->build_dir; SBUF(new_cwd); path_join(wk, &new_cwd, get_cstr(wk, old_cwd), get_cstr(wk, an[0].val)); current_project(wk)->cwd = make_str(wk, new_cwd.buf); path_join(wk, &build_dir, get_cstr(wk, old_build_dir), get_cstr(wk, an[0].val)); current_project(wk)->build_dir = sbuf_into_str(wk, &build_dir); bool ret = false; if (!wk->in_analyzer) { if (!fs_mkdir_p(build_dir.buf)) { goto ret; } } path_push(wk, &new_cwd, "meson.build"); ret = wk->eval_project_file(wk, new_cwd.buf, false); ret: current_project(wk)->cwd = old_cwd; current_project(wk)->build_dir = old_build_dir; return ret; } static bool func_configuration_data(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_dict }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } make_obj(wk, res, obj_configuration_data); if (ao[0].set) { get_obj_configuration_data(wk, *res)->dict = ao[0].val; } else { obj dict; make_obj(wk, &dict, obj_dict); get_obj_configuration_data(wk, *res)->dict = dict; } return true; } static bool func_add_test_setup(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_env, kw_exclude_suites, kw_exe_wrapper, kw_gdb, kw_is_default, kw_timeout_multiplier, }; struct args_kw akw[] = { [kw_env] = { "env", tc_coercible_env, }, [kw_exclude_suites] = { "exclude_suites", ARG_TYPE_ARRAY_OF | obj_string }, [kw_exe_wrapper] = { "exe_wrapper", tc_command_array }, [kw_gdb] = { "gdb", obj_bool }, [kw_is_default] = { "is_default", obj_bool }, [kw_timeout_multiplier] = { "timeout_multiplier", obj_number }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj test_setup; make_obj(wk, &test_setup, obj_array); obj env = 0; if (akw[kw_env].set && !coerce_environment_from_kwarg(wk, &akw[kw_env], false, &env)) { return false; } obj exe_wrapper = 0; if (akw[kw_exe_wrapper].set && !arr_to_args(wk, arr_to_args_build_target | arr_to_args_custom_target, akw[kw_exe_wrapper].val, &exe_wrapper)) { return false; } /* [name, env, exclude_suites, exe_wrapper, is_default, timeout_multiplier] */ obj_array_push(wk, test_setup, an[0].val); obj_array_push(wk, test_setup, env); obj_array_push(wk, test_setup, akw[kw_exclude_suites].val); obj_array_push(wk, test_setup, exe_wrapper); obj_array_push(wk, test_setup, akw[kw_is_default].val); obj_array_push(wk, test_setup, akw[kw_timeout_multiplier].val); if (!current_project(wk)->test_setups) { make_obj(wk, ¤t_project(wk)->test_setups, obj_array); } obj_array_push(wk, current_project(wk)->test_setups, test_setup); return true; } struct add_test_depends_ctx { struct obj_test *t; bool from_custom_tgt; }; static enum iteration_result add_test_depends_iter(struct workspace *wk, void *_ctx, obj val) { SBUF(rel); struct add_test_depends_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_string: case obj_external_program: break; case obj_file: if (!ctx->from_custom_tgt) { break; } path_relative_to(wk, &rel, wk->build_root, get_file_path(wk, val)); obj_array_push(wk, ctx->t->depends, sbuf_into_str(wk, &rel)); break; case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); path_relative_to(wk, &rel, wk->build_root, get_cstr(wk, tgt->build_path)); obj_array_push(wk, ctx->t->depends, sbuf_into_str(wk, &rel)); break; } case obj_custom_target: ctx->from_custom_tgt = true; if (!obj_array_foreach(wk, get_obj_custom_target(wk, val)->output, ctx, add_test_depends_iter)) { return ir_err; } ctx->from_custom_tgt = false; break; default: UNREACHABLE; } return ir_cont; } static bool add_test_common(struct workspace *wk, uint32_t args_node, enum test_category cat) { struct args_norm an[] = { { obj_string }, { tc_build_target | tc_external_program | tc_file }, ARG_TYPE_NULL }; enum kwargs { kw_args, kw_workdir, kw_depends, kw_should_fail, kw_env, kw_suite, kw_priority, kw_timeout, kw_protocol, kw_is_parallel, kw_verbose, }; struct args_kw akw[] = { [kw_args] = { "args", tc_command_array, }, [kw_workdir] = { "workdir", obj_string, }, [kw_depends] = { "depends", tc_depends_kw, }, [kw_should_fail] = { "should_fail", obj_bool, }, [kw_env] = { "env", tc_coercible_env, }, [kw_suite] = { "suite", ARG_TYPE_ARRAY_OF | obj_string }, [kw_priority] = { "priority", obj_number, }, [kw_timeout] = { "timeout", obj_number, }, [kw_protocol] = { "protocol", obj_string, }, [kw_is_parallel] = { 0 }, [kw_verbose] = { "verbose", obj_bool }, 0 }; if (cat == test_category_test) { akw[kw_is_parallel] = (struct args_kw){ "is_parallel", obj_bool, }; } if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } enum test_protocol protocol = test_protocol_exitcode; if (akw[kw_protocol].set) { const char *protocol_names[] = { [test_protocol_exitcode] = "exitcode", [test_protocol_tap] = "tap", [test_protocol_gtest] = "gtest", [test_protocol_rust] = "rust", }; for (protocol = 0; protocol < ARRAY_LEN(protocol_names); ++protocol) { if (str_eql(get_str(wk, akw[kw_protocol].val), &WKSTR(protocol_names[protocol]))) { break; } } if (protocol == ARRAY_LEN(protocol_names)) { interp_error(wk, akw[kw_protocol].node, "invalid protocol %o", akw[kw_protocol].val); return false; } if (protocol == test_protocol_gtest || protocol == test_protocol_rust) { interp_warning(wk, akw[kw_protocol].node, "unsupported protocol %o, falling back to 'exitcode'", akw[kw_protocol].val); protocol = test_protocol_exitcode; } } obj exe; if (!coerce_executable(wk, an[1].node, an[1].val, &exe)) { return false; } obj args = 0; if (akw[kw_args].set) { if (!arr_to_args(wk, arr_to_args_build_target | arr_to_args_custom_target, akw[kw_args].val, &args)) { return false; } } obj test; make_obj(wk, &test, obj_test); struct obj_test *t = get_obj_test(wk, test); if (!coerce_environment_from_kwarg(wk, &akw[kw_env], false, &t->env)) { return false; } t->name = an[0].val; t->exe = exe; t->args = args; t->should_fail = akw[kw_should_fail].set && get_obj_bool(wk, akw[kw_should_fail].val); t->suites = akw[kw_suite].val; t->workdir = akw[kw_workdir].val; t->timeout = akw[kw_timeout].val; t->priority = akw[kw_priority].val; t->category = cat; t->protocol = protocol; t->verbose = akw[kw_verbose].set && get_obj_bool(wk, akw[kw_verbose].val); if (akw[kw_is_parallel].key) { t->is_parallel = akw[kw_is_parallel].set ? get_obj_bool(wk, akw[kw_is_parallel].val) : true; } struct add_test_depends_ctx deps_ctx = { .t = t }; make_obj(wk, &t->depends, obj_array); add_test_depends_iter(wk, &deps_ctx, an[1].val); if (akw[kw_depends].set) { obj_array_foreach(wk, akw[kw_depends].val, &deps_ctx, add_test_depends_iter); } if (akw[kw_args].set) { obj_array_foreach(wk, akw[kw_args].val, &deps_ctx, add_test_depends_iter); } obj_array_push(wk, current_project(wk)->tests, test); return true; } static bool func_test(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { return add_test_common(wk, args_node, test_category_test); } static bool func_benchmark(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { return add_test_common(wk, args_node, test_category_benchmark); } struct join_paths_ctx { uint32_t node; struct sbuf *buf; }; static enum iteration_result join_paths_iter(struct workspace *wk, void *_ctx, obj val) { struct join_paths_ctx *ctx = _ctx; if (!typecheck(wk, ctx->node, val, obj_string)) { return ir_err; } path_push(wk, ctx->buf, get_cstr(wk, val)); return ir_cont; } static bool func_join_paths(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(join_paths_buf); struct join_paths_ctx ctx = { .buf = &join_paths_buf, .node = args_node, }; if (!obj_array_foreach_flat(wk, an[0].val, &ctx, join_paths_iter)) { return false; } *res = sbuf_into_str(wk, ctx.buf); return true; } static enum iteration_result environment_set_initial_iter(struct workspace *wk, void *_ctx, obj key, obj val) { obj env = *(obj *)_ctx; if (!environment_set(wk, env, environment_set_mode_set, key, val, 0)) { return ir_err; } return ir_cont; } static bool func_environment(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_dict }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } make_obj(wk, res, obj_environment); struct obj_environment *d = get_obj_environment(wk, *res); make_obj(wk, &d->actions, obj_array); if (ao[0].set) { if (!typecheck_dict(wk, ao[0].node, ao[0].val, obj_string)) { return false; } obj_dict_foreach(wk, ao[0].val, res, environment_set_initial_iter); } return true; } static bool func_import(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_disabler, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_disabler] = { "disabler", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (wk->in_analyzer) { // If we are in the analyzer, don't create a disabler here so // that the custom not found module logic can be used akw[kw_disabler].set = false; } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } enum module mod = 0; bool found = false, has_impl = false; if (requirement != requirement_skip) { if (module_lookup(get_cstr(wk, an[0].val), &mod, &has_impl)) { found = true; } else if (requirement == requirement_required) { interp_error(wk, an[0].node, "module not found"); return false; } } if (!has_impl) { if (requirement != requirement_required || wk->in_analyzer) { found = false; has_impl = false; } else { LOG_W("importing unimplemented module '%s'", get_cstr(wk, an[0].val)); } } if (!found && akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val)) { *res = disabler_id; return true; } make_obj(wk, res, obj_module); struct obj_module *m = get_obj_module(wk, *res); m->module = mod; m->found = found; m->has_impl = has_impl; return true; } static bool func_is_disabler(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; disabler_among_args_immunity = true; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } disabler_among_args_immunity = false; make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, an[0].val == disabler_id); return true; } static bool func_disabler(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = disabler_id; return true; } static bool func_set_variable(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { tc_any }, ARG_TYPE_NULL }; disabler_among_args_immunity = true; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } disabler_among_args_immunity = false; wk->assign_variable(wk, get_cstr(wk, an[0].val), an[1].val, args_node); return true; } static bool func_unset_variable(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const char *varname = get_cstr(wk, an[0].val); obj _val; if (wk->get_variable(wk, varname, &_val, wk->cur_project)) { wk->unassign_variable(wk, varname); } else { interp_error(wk, an[0].node, "cannot unset undefined variable: %o", an[0].val); return false; } return true; } static bool func_get_variable(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; disabler_among_args_immunity = true; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } disabler_among_args_immunity = false; if (an[0].val == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, an[0].node, an[0].val, obj_string)) { return false; } if (!wk->get_variable(wk, get_cstr(wk, an[0].val), res, wk->cur_project)) { if (ao[0].set) { *res = ao[0].val; } else { interp_error(wk, an[0].node, "undefined object %o", an[0].val); return false; } } return true; } static bool func_is_variable(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; disabler_among_args_immunity = true; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } disabler_among_args_immunity = false; obj dont_care; make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, wk->get_variable(wk, get_cstr(wk, an[0].val), &dont_care, wk->cur_project)); return true; } static bool func_subdir_done(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } wk->subdir_done = true; return true; } static bool func_summary(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; enum kwargs { kw_section, kw_bool_yn, // ignored kw_list_sep, // ignored }; struct args_kw akw[] = { [kw_section] = { "section", obj_string, }, [kw_bool_yn] = { "bool_yn", obj_bool, }, [kw_list_sep] = { "list_sep", obj_string, }, 0 }; if (!interp_args(wk, args_node, an, ao, akw)) { return false; } obj sec = akw[kw_section].set ? akw[kw_section].val : make_str(wk, ""); obj dict; if (ao[0].set) { if (!typecheck(wk, an[0].node, an[0].val, obj_string)) { return false; } make_obj(wk, &dict, obj_dict); obj_dict_set(wk, dict, an[0].val, ao[0].val); } else { if (!typecheck(wk, an[0].node, an[0].val, obj_dict)) { return false; } dict = an[0].val; } obj prev; if (obj_dict_index(wk, current_project(wk)->summary, sec, &prev)) { obj ndict; obj_dict_merge(wk, prev, dict, &ndict); dict = ndict; } obj_dict_set(wk, current_project(wk)->summary, sec, dict); return true; } static obj make_alias_target(struct workspace *wk, obj name, obj deps) { assert(get_obj_type(wk, name) == obj_string && "Alias target name must be a string."); assert(get_obj_type(wk, deps) == obj_array && "Alias target list must be an array."); obj id; make_obj(wk, &id, obj_alias_target); struct obj_alias_target *alias_tgt = get_obj_alias_target(wk, id); alias_tgt->name = name; alias_tgt->depends = deps; return id; } struct alias_target_iter_ctx { obj deps; }; static enum iteration_result push_alias_target_deps_iter(struct workspace *wk, void *_ctx, obj val) { struct alias_target_iter_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_alias_target: case obj_build_target: case obj_custom_target: obj_array_push(wk, ctx->deps, val); break; default: interp_error(wk, val, "expected target but got: %s", obj_type_to_s(t)); return ir_err; } return ir_cont; } static bool func_alias_target(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { ARG_TYPE_GLOB | tc_build_target | tc_custom_target | tc_alias_target | tc_both_libs }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } L("adding alias target '%s'", get_cstr(wk, an[0].val)); obj deps_id; make_obj(wk, &deps_id, obj_array); struct alias_target_iter_ctx iter_ctx = { .deps = deps_id, }; if (!obj_array_foreach_flat(wk, an[1].val, &iter_ctx, push_alias_target_deps_iter)) { return false; } *res = make_alias_target(wk, an[0].val, deps_id); obj_array_push(wk, current_project(wk)->targets, *res); return true; } bool func_range_common(struct workspace *wk, uint32_t args_node, struct range_params *res) { struct args_norm an[] = { { obj_number }, ARG_TYPE_NULL }; struct args_norm ao[] = { { obj_number }, { obj_number }, { obj_number }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } int64_t n = get_obj_number(wk, an[0].val); if (!rangecheck(wk, an[0].node, 0, UINT32_MAX, n)) { return false; } res->start = n; if (ao[0].set) { int64_t n = get_obj_number(wk, ao[0].val); if (!rangecheck(wk, ao[0].node, res->start, UINT32_MAX, n)) { return false; } res->stop = n; } else { res->stop = res->start; res->start = 0; } if (ao[1].set) { int64_t n = get_obj_number(wk, ao[1].val); if (!rangecheck(wk, ao[1].node, 1, UINT32_MAX, n)) { return false; } res->step = n; } else { res->step = 1; } return true; } static bool func_range(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct range_params params = { 0 }; if (!func_range_common(wk, args_node, ¶ms)) { return false; } make_obj(wk, res, obj_array); uint32_t i; for (i = params.start; i < params.stop; i += params.step) { obj num; make_obj(wk, &num, obj_number); set_obj_number(wk, num, i); obj_array_push(wk, *res, num); } return true; } /* * muon extension funcitons */ static bool func_p(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj_fprintf(wk, log_file(), "%o\n", an[0].val); return true; } static bool func_dbg(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } LOG_I("entering debugger, type \\help for help"); repl(wk, true); return true; } static bool func_serial_load(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj str; coerce_string(wk, an[0].node, an[0].val, &str); FILE *f; if (str_eql(get_str(wk, str), &WKSTR("-"))) { f = stdin; } else if (!(f = fs_fopen(get_cstr(wk, str), "rb"))) { return false; } bool ret = false; if (!serial_load(wk, res, f)) { goto ret; } if (!fs_fclose(f)) { goto ret; } ret = true; ret: return ret; } static bool func_serial_dump(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj str; coerce_string(wk, an[0].node, an[0].val, &str); FILE *f; if (!(f = fs_fopen(get_cstr(wk, str), "wb"))) { return false; } bool ret = false; if (!serial_dump(wk, an[1].val, f)) { goto ret; } if (!fs_fclose(f)) { goto ret; } ret = true; ret: return ret; } const struct func_impl_name impl_tbl_kernel[] = { { "add_global_arguments", func_add_global_arguments }, { "add_global_link_arguments", func_add_global_link_arguments }, { "add_languages", func_add_languages, tc_bool }, { "add_project_arguments", func_add_project_arguments }, { "add_project_dependencies", func_add_project_dependencies }, { "add_project_link_arguments", func_add_project_link_arguments }, { "add_test_setup", func_add_test_setup }, { "alias_target", func_alias_target, tc_alias_target }, { "assert", func_assert }, { "benchmark", func_benchmark }, { "both_libraries", func_both_libraries, tc_both_libs }, { "build_target", func_build_target, tc_build_target | tc_both_libs }, { "configuration_data", func_configuration_data, tc_configuration_data }, { "configure_file", func_configure_file, tc_file }, { "custom_target", func_custom_target, tc_custom_target }, { "debug", func_debug }, { "declare_dependency", func_declare_dependency, tc_dependency }, { "dependency", func_dependency, tc_dependency }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_error }, { "executable", func_executable, tc_build_target }, { "files", func_files, tc_array }, { "find_program", func_find_program, tc_external_program }, { "generator", func_generator, tc_generator }, { "get_option", func_get_option, tc_string | tc_number | tc_bool | tc_feature_opt | tc_array, }, { "get_variable", func_get_variable, tc_any, true }, { "import", func_import, tc_module, true }, { "include_directories", func_include_directories, tc_array }, { "install_data", func_install_data }, { "install_emptydir", func_install_emptydir }, { "install_headers", func_install_headers }, { "install_man", func_install_man }, { "install_subdir", func_install_subdir }, { "install_symlink", func_install_symlink }, { "is_disabler", func_is_disabler, tc_bool, true }, { "is_variable", func_is_variable, tc_bool, true }, { "join_paths", func_join_paths, tc_string, true }, { "library", func_library, tc_build_target | tc_both_libs }, { "message", func_message }, { "project", func_project }, { "range", func_range, tc_array, true }, { "run_command", func_run_command, tc_run_result }, { "run_target", func_run_target, tc_custom_target }, { "set_variable", func_set_variable, 0, true }, { "shared_library", func_shared_library, tc_build_target }, { "shared_module", func_shared_module, tc_build_target }, { "static_library", func_static_library, tc_build_target }, { "subdir", func_subdir, 0, true }, { "subdir_done", func_subdir_done }, { "subproject", func_subproject, tc_subproject }, { "summary", func_summary }, { "test", func_test }, { "unset_variable", func_unset_variable, 0, true }, { "vcs_tag", func_vcs_tag, tc_custom_target }, { "warning", func_warning }, // non-standard muon extensions { "dbg", func_dbg, .extension = true }, { "p", func_p, 0, true, .extension = true }, { NULL, NULL }, }; const struct func_impl_name impl_tbl_kernel_internal[] = { { "assert", func_assert }, { "configuration_data", func_configuration_data, tc_configuration_data }, { "disabler", func_disabler, tc_disabler }, { "environment", func_environment, tc_environment }, { "error", func_error }, { "files", func_files, tc_array }, { "find_program", func_find_program, tc_external_program }, { "get_variable", func_get_variable, tc_any, true }, { "import", func_import, tc_module, true }, { "is_disabler", func_is_disabler, tc_bool, true }, { "is_variable", func_is_variable, tc_bool, true }, { "join_paths", func_join_paths, tc_string, true }, { "message", func_message }, { "range", func_range, tc_array, true }, { "run_command", func_run_command, tc_run_result, .fuzz_unsafe = true }, { "set_variable", func_set_variable, 0, true }, { "unset_variable", func_unset_variable, 0, true }, { "warning", func_warning }, // non-standard muon extensions { "dbg", func_dbg }, { "p", func_p }, { "serial_load", func_serial_load, tc_any }, { "serial_dump", func_serial_dump, .fuzz_unsafe = true }, { NULL, NULL }, }; const struct func_impl_name impl_tbl_kernel_opts[] = { { "option", func_option, 0, true }, // non-standard muon extensions { "dbg", func_dbg }, { "p", func_p }, { NULL, NULL }, }; muon-0.2.0/src/functions/kernel/000077500000000000000000000000001441402162300165435ustar00rootroot00000000000000muon-0.2.0/src/functions/kernel/build_target.c000066400000000000000000000702751441402162300213670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "coerce.h" #include "error.h" #include "functions/build_target.h" #include "functions/file.h" #include "functions/generator.h" #include "functions/kernel/build_target.h" #include "functions/kernel/dependency.h" #include "functions/machine.h" #include "install.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" enum build_target_kwargs { bt_kw_sources, bt_kw_include_directories, bt_kw_implicit_include_directories, bt_kw_dependencies, bt_kw_install, bt_kw_install_dir, bt_kw_install_mode, bt_kw_install_tag, bt_kw_link_with, bt_kw_link_whole, bt_kw_version, bt_kw_build_by_default, bt_kw_extra_files, // TODO bt_kw_target_type, bt_kw_name_prefix, bt_kw_name_suffix, bt_kw_soversion, bt_kw_link_depends, bt_kw_objects, bt_kw_pic, bt_kw_pie, bt_kw_build_rpath, bt_kw_install_rpath, bt_kw_export_dynamic, bt_kw_vs_module_defs, // TODO bt_kw_gnu_symbol_visibility, bt_kw_native, // TODO bt_kw_darwin_versions, // TODO bt_kw_implib, // TODO bt_kw_gui_app, // TODO bt_kw_link_language, // TODO bt_kw_win_subsystem, // TODO bt_kw_override_options, /* lang args */ bt_kw_c_pch, // TODO bt_kw_cpp_pch, // TODO bt_kw_c_args, bt_kw_cpp_args, bt_kw_objc_args, bt_kw_masm_args, // TODO bt_kw_nasm_args, // TODO bt_kw_link_args, bt_kwargs_count, }; static enum iteration_result determine_linker_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_build_target *tgt = _ctx; enum compiler_language fl; if (!filename_to_compiler_language(get_file_path(wk, val), &fl)) { /* LOG_E("unable to determine language for '%s'", get_cstr(wk, src->dat.file)); */ return ir_cont; } tgt->dep_internal.link_language = coalesce_link_languages(tgt->dep_internal.link_language, fl); return ir_cont; } static enum iteration_result determine_linker_from_objects_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_build_target *tgt = _ctx; enum compiler_language fl; const struct str *o = get_str(wk, *get_obj_file(wk, val)); if (!str_endswith(o, &WKSTR(".o"))) { return ir_cont; } SBUF(path); sbuf_pushs(wk, &path, o->s); path.len -= 2; path.buf[path.len] = 0; if (!filename_to_compiler_language(path.buf, &fl)) { /* LOG_E("unable to determine language for '%s'", get_cstr(wk, src->dat.file)); */ return ir_cont; } tgt->dep_internal.link_language = coalesce_link_languages(tgt->dep_internal.link_language, fl); return ir_cont; } static bool build_tgt_determine_linker(struct workspace *wk, uint32_t err_node, struct obj_build_target *tgt) { if (!obj_array_foreach(wk, tgt->src, tgt, determine_linker_iter)) { return ir_err; } if (!obj_array_foreach(wk, tgt->objects, tgt, determine_linker_from_objects_iter)) { return ir_err; } if (!tgt->dep_internal.link_language) { enum compiler_language clink_langs[] = { compiler_language_c, compiler_language_cpp, }; obj comp; uint32_t i; for (i = 0; i < ARRAY_LEN(clink_langs); ++i) { if (obj_dict_geti(wk, current_project(wk)->compilers, clink_langs[i], &comp)) { tgt->dep_internal.link_language = clink_langs[i]; break; } } } if (!tgt->dep_internal.link_language) { interp_error(wk, err_node, "unable to determine linker for target"); return false; } return true; } struct process_build_tgt_sources_ctx { uint32_t err_node; obj tgt_id; obj res; bool implicit_include_directories; }; static bool process_source_include(struct workspace *wk, struct process_build_tgt_sources_ctx *ctx, obj val) { const char *src = get_file_path(wk, val); if (!path_is_subpath(wk->build_root, src)) { return true; } SBUF(dir); SBUF(path); path_relative_to(wk, &path, wk->build_root, src); struct obj_build_target *tgt = get_obj_build_target(wk, ctx->tgt_id); obj_array_push(wk, tgt->dep_internal.order_deps, sbuf_into_str(wk, &path)); if (!ctx->implicit_include_directories) { return true; } path_dirname(wk, &dir, src); obj inc; make_obj(wk, &inc, obj_include_directory); struct obj_include_directory *d = get_obj_include_directory(wk, inc); d->path = sbuf_into_str(wk, &dir); obj_array_push(wk, tgt->dep_internal.include_directories, inc); return true; } static enum iteration_result build_tgt_push_source_files_iter(struct workspace *wk, void *_ctx, obj val) { struct process_build_tgt_sources_ctx *ctx = _ctx; struct obj_build_target *tgt = get_obj_build_target(wk, ctx->tgt_id); if (file_is_linkable(wk, val)) { obj_array_push(wk, tgt->dep_internal.link_with, val); return ir_cont; } enum compiler_language lang; if (!filename_to_compiler_language(get_file_path(wk, val), &lang) || languages[lang].is_header) { // process every file that is either a header, or isn't // recognized, as a header if (!process_source_include(wk, ctx, val)) { return ir_err; } return ir_cont; } else if (languages[lang].is_linkable) { obj_array_push(wk, tgt->objects, val); return ir_cont; } obj n; if (obj_dict_geti(wk, tgt->required_compilers, lang, &n)) { obj_dict_seti(wk, tgt->required_compilers, lang, n + 1); } else { obj_dict_seti(wk, tgt->required_compilers, lang, 1); } obj_array_push(wk, ctx->res, val); return ir_cont; } static enum iteration_result process_build_tgt_sources_iter(struct workspace *wk, void *_ctx, obj val) { obj res; struct process_build_tgt_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, true, &res)) { return ir_err; } break; default: { if (!coerce_files(wk, ctx->err_node, val, &res)) { return ir_err; } break; } } obj_array_foreach(wk, res, ctx, build_tgt_push_source_files_iter); return ir_cont; } static bool type_from_kw(struct workspace *wk, uint32_t node, obj t, enum tgt_type *res) { const char *tgt_type = get_cstr(wk, t); struct { char *name; enum tgt_type type; } tgt_tbl[] = { { "executable", tgt_executable, }, { "shared_library", tgt_dynamic_library, }, { "shared_module", tgt_shared_module, }, { "static_library", tgt_static_library, }, { "both_libraries", tgt_dynamic_library | tgt_static_library, }, { "library", get_option_default_library(wk), }, { 0 }, }; uint32_t i; for (i = 0; tgt_tbl[i].name; ++i) { if (strcmp(tgt_type, tgt_tbl[i].name) == 0) { *res = tgt_tbl[i].type; break; } } if (!tgt_tbl[i].name) { interp_error(wk, node, "unsupported target type '%s'", tgt_type); return false; } return true; } static void setup_soname(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj sover, obj ver) { char soversion[BUF_SIZE_1k] = { "." }; bool have_soversion = false; if (sover) { have_soversion = true; strncpy(&soversion[1], get_cstr(wk, sover), BUF_SIZE_1k - 2); } else if (ver) { have_soversion = true; strncpy(&soversion[1], get_cstr(wk, ver), BUF_SIZE_1k - 2); char *p; if ((p = strchr(&soversion[1], '.'))) { *p = 0; } } tgt->soname = make_strf(wk, "%s%s", plain_name, have_soversion ? soversion : ""); } static void setup_dllname(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj dllver, obj ver) { if (dllver) { tgt->soname = make_strf(wk, "%s-%s.dll", plain_name, get_cstr(wk, dllver)); return; } else if (ver) { char buf[BUF_SIZE_1k]; strncpy(buf, get_cstr(wk, ver), sizeof(buf) - 1); char *tmp = strchr(buf, '.'); if (tmp) { *tmp = '\0'; tgt->soname = make_strf(wk, "%s-%s.dll", plain_name, buf); return; } } tgt->soname = make_strf(wk, "%s.dll", plain_name); } static bool setup_shared_object_symlinks(struct workspace *wk, struct obj_build_target *tgt, const char *plain_name, obj *plain_name_install, obj *soname_install) { SBUF(soname_symlink); SBUF(plain_name_symlink); if (!fs_mkdir_p(get_cstr(wk, tgt->build_dir))) { return false; } if (!str_eql(get_str(wk, tgt->build_name), get_str(wk, tgt->soname))) { path_join(wk, &soname_symlink, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->soname)); if (!fs_make_symlink(get_cstr(wk, tgt->build_name), soname_symlink.buf, true)) { return false; } *soname_install = sbuf_into_str(wk, &soname_symlink); } if (!str_eql(get_str(wk, tgt->soname), &WKSTR(plain_name))) { path_join(wk, &plain_name_symlink, get_cstr(wk, tgt->build_dir), plain_name); if (!fs_make_symlink(get_cstr(wk, tgt->soname), plain_name_symlink.buf, true)) { return false; } *plain_name_install = sbuf_into_str(wk, &plain_name_symlink); } return true; } static bool determine_target_build_name(struct workspace *wk, struct obj_build_target *tgt, obj sover, obj ver, obj name_pre, obj name_suff, char plain_name[BUF_SIZE_2k]) { char ver_dll[BUF_SIZE_1k]; const char *pref, *suff, *ver_suff = NULL; enum machine_system sys = machine_system(); *ver_dll = '\0'; switch (tgt->type) { case tgt_executable: pref = ""; if (sys == machine_system_windows || sys == machine_system_cygwin) { suff = "exe"; } else { suff = NULL; } break; case tgt_static_library: if (sys == machine_system_cygwin) { pref = "cyg"; } else { pref = "lib"; } suff = "a"; break; case tgt_shared_module: case tgt_dynamic_library: if (sys == machine_system_cygwin) { pref = "cyg"; } else { pref = "lib"; } if (sys == machine_system_windows || sys == machine_system_cygwin) { suff = "dll"; if (sover) { strncpy(ver_dll, get_cstr(wk, sover), sizeof(ver_dll) - 1); } else if (ver) { strncpy(ver_dll, get_cstr(wk, ver), sizeof(ver_dll) - 1); char *ver_tmp = strchr(ver_dll, '.'); if (ver_tmp) { *ver_tmp = '\0'; } else { *ver_dll = '\0'; } } } else if (sys == machine_system_darwin) { suff = "dylib"; } else { suff = "so"; if (ver) { ver_suff = get_cstr(wk, ver); } else if (sover) { ver_suff = get_cstr(wk, sover); } } break; default: assert(false && "unreachable"); return false; } if (name_pre) { pref = get_cstr(wk, name_pre); } if (name_suff) { suff = get_cstr(wk, name_suff); } if (sys == machine_system_windows || sys == machine_system_cygwin) { snprintf(plain_name, BUF_SIZE_2k, "%s%s", pref, get_cstr(wk, tgt->name)); tgt->build_name = make_strf(wk, "%s%s%s%s%s", plain_name, *ver_dll ? "-" : "", *ver_dll ? ver_dll : "", suff ? "." : "", suff ? suff : ""); } else { snprintf(plain_name, BUF_SIZE_2k, "%s%s%s%s", pref, get_cstr(wk, tgt->name), suff ? "." : "", suff ? suff : ""); tgt->build_name = make_strf(wk, "%s%s%s", plain_name, ver_suff ? "." : "", ver_suff ? ver_suff : ""); } return true; } static bool create_target(struct workspace *wk, struct args_norm *an, struct args_kw *akw, enum tgt_type type, bool ignore_sources, obj *res) { char plain_name[BUF_SIZE_2k + 1] = { 0 }; make_obj(wk, res, obj_build_target); struct obj_build_target *tgt = get_obj_build_target(wk, *res); tgt->type = type; tgt->name = an[0].val; tgt->cwd = current_project(wk)->cwd; tgt->build_dir = current_project(wk)->build_dir; make_obj(wk, &tgt->args, obj_dict); make_obj(wk, &tgt->src, obj_array); make_obj(wk, &tgt->required_compilers, obj_dict); build_dep_init(wk, &tgt->dep_internal); { // linker args (process before dependencies so link_with libs come first on link line if (akw[bt_kw_link_with].set) { if (!dep_process_link_with(wk, akw[bt_kw_link_with].node, akw[bt_kw_link_with].val, &tgt->dep_internal)) { return false; } } if (akw[bt_kw_link_whole].set) { if (!dep_process_link_whole(wk, akw[bt_kw_link_whole].node, akw[bt_kw_link_whole].val, &tgt->dep_internal)) { return false; } } } if (akw[bt_kw_dependencies].set) { dep_process_deps(wk, akw[bt_kw_dependencies].val, &tgt->dep_internal); } if (akw[bt_kw_override_options].set) { // override options if (!parse_and_set_override_options(wk, akw[bt_kw_override_options].node, akw[bt_kw_override_options].val, &tgt->override_options)) { return false; } } { // build target flags { // pic bool pic = false; if (akw[bt_kw_pic].set) { pic = get_obj_bool(wk, akw[bt_kw_pic].val); if (!pic && tgt->type & (tgt_dynamic_library | tgt_shared_module)) { interp_error(wk, akw[bt_kw_pic].node, "shared libraries must be compiled as pic"); return false; } } else { bool staticpic = get_option_bool(wk, tgt->override_options, "b_staticpic", true); if (tgt->type & tgt_static_library) { pic = staticpic; } else if (tgt->type & (tgt_dynamic_library | tgt_shared_module)) { pic = true; } } if (pic) { tgt->flags |= build_tgt_flag_pic; } } { // pie bool pie = false; if (akw[bt_kw_pie].set) { pie = get_obj_bool(wk, akw[bt_kw_pie].set); if (pie && (tgt->type & tgt_executable) != tgt_executable) { interp_error(wk, akw[bt_kw_pie].node, "pie cannot be set for non-executables"); return false; } } else if ((tgt->type & tgt_executable) == tgt_executable) { pie = get_option_bool(wk, tgt->override_options, "b_pie", false); } if (pie) { tgt->flags |= build_tgt_flag_pie; } } if (akw[bt_kw_export_dynamic].set && get_obj_bool(wk, akw[bt_kw_export_dynamic].val)) { tgt->flags |= build_tgt_flag_export_dynamic; } if (!akw[bt_kw_build_by_default].set || get_obj_bool(wk, akw[bt_kw_build_by_default].val)) { tgt->flags |= build_tgt_flag_build_by_default; } struct args_kw *vis = &akw[bt_kw_gnu_symbol_visibility]; if (vis->set && get_str(wk, vis->val)->len) { const struct str *str = get_str(wk, vis->val); if (str_eql(str, &WKSTR("default"))) { tgt->visibility = compiler_visibility_default; } else if (str_eql(str, &WKSTR("hidden"))) { tgt->visibility = compiler_visibility_hidden; } else if (str_eql(str, &WKSTR("internal"))) { tgt->visibility = compiler_visibility_internal; } else if (str_eql(str, &WKSTR("protected"))) { tgt->visibility = compiler_visibility_protected; } else if (str_eql(str, &WKSTR("inlineshidden"))) { tgt->visibility = compiler_visibility_inlineshidden; } else { interp_error(wk, vis->node, "unknown visibility '%s'", get_cstr(wk, vis->val)); return false; } tgt->flags |= build_tgt_flag_visibility; } } obj sover = 0; if (akw[bt_kw_soversion].set) { if (!coerce_num_to_string(wk, akw[bt_kw_soversion].node, akw[bt_kw_soversion].val, &sover)) { return false; } } if (!determine_target_build_name(wk, tgt, sover, akw[bt_kw_version].val, akw[bt_kw_name_prefix].val, akw[bt_kw_name_suffix].val, plain_name)) { return false; } { /* tgt_build_path */ SBUF(path); path_join(wk, &path, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); tgt->build_path = make_str(wk, path.buf); sbuf_pushs(wk, &path, ".p"); tgt->private_path = sbuf_into_str(wk, &path); } bool implicit_include_directories = akw[bt_kw_implicit_include_directories].set ? get_obj_bool(wk, akw[bt_kw_implicit_include_directories].val) : true; { // sources if (akw[bt_kw_objects].set) { if (!coerce_files(wk, akw[bt_kw_objects].node, akw[bt_kw_objects].val, &tgt->objects)) { return false; } obj deduped; obj_array_dedup(wk, tgt->objects, &deduped); tgt->objects = deduped; } else { make_obj(wk, &tgt->objects, obj_array); } if (!ignore_sources) { obj sources = an[1].val; if (akw[bt_kw_sources].set) { obj_array_extend(wk, sources, akw[bt_kw_sources].val); } obj_array_extend(wk, sources, tgt->dep_internal.sources); struct process_build_tgt_sources_ctx ctx = { .err_node = an[1].node, .res = tgt->src, .tgt_id = *res, .implicit_include_directories = implicit_include_directories, }; if (!obj_array_foreach_flat(wk, sources, &ctx, process_build_tgt_sources_iter)) { return false; } obj deduped; obj_array_dedup(wk, tgt->src, &deduped); tgt->src = deduped; } if (!get_obj_array(wk, tgt->src)->len && !get_obj_array(wk, tgt->objects)->len && !akw[bt_kw_link_whole].set && tgt->type != tgt_static_library) { uint32_t node = akw[bt_kw_sources].set? akw[bt_kw_sources].node : an[1].node; interp_error(wk, node, "target declared with no linkable sources"); return false; } } { // include directories obj inc_dirs; make_obj(wk, &inc_dirs, obj_array); uint32_t node = an[0].node; // TODO: not a very informative error node if (implicit_include_directories) { obj_array_push(wk, inc_dirs, current_project(wk)->cwd); } if (akw[bt_kw_include_directories].set) { node = akw[bt_kw_include_directories].node; obj_array_extend(wk, inc_dirs, akw[bt_kw_include_directories].val); } obj coerced; if (!coerce_include_dirs(wk, node, inc_dirs, false, &coerced)) { return false; } obj_array_extend_nodup(wk, coerced, tgt->dep_internal.include_directories); tgt->dep_internal.include_directories = coerced; } { // compiler args static struct { enum build_target_kwargs kw; enum compiler_language l; } lang_args[] = { { bt_kw_c_args, compiler_language_c }, { bt_kw_cpp_args, compiler_language_cpp }, { bt_kw_objc_args, compiler_language_objc }, { bt_kw_nasm_args, compiler_language_nasm }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(lang_args); ++i) { if (akw[lang_args[i].kw].set) { obj_dict_seti(wk, tgt->args, lang_args[i].l, akw[lang_args[i].kw].val); } } } obj soname_install = 0, plain_name_install = 0; // soname handling if (type & (tgt_dynamic_library | tgt_shared_module)) { enum machine_system sys = machine_system(); if (sys == machine_system_windows || sys == machine_system_cygwin) { setup_dllname(wk, tgt, plain_name, sover, akw[bt_kw_version].val); } else { setup_soname(wk, tgt, plain_name, sover, akw[bt_kw_version].val); if (type == tgt_dynamic_library) { if (!setup_shared_object_symlinks(wk, tgt, plain_name, &plain_name_install, &soname_install)) { return false; } } } } // link depends if (akw[bt_kw_link_depends].set) { obj depends; if (!coerce_files(wk, akw[bt_kw_link_depends].node, akw[bt_kw_link_depends].val, &depends)) { return false; } tgt->link_depends = depends; } if (akw[bt_kw_install].set && get_obj_bool(wk, akw[bt_kw_install].val)) { tgt->flags |= build_tgt_flag_installed; obj install_dir = 0; if (akw[bt_kw_install_dir].set) { install_dir = akw[bt_kw_install_dir].val; } else { switch (type) { case tgt_executable: get_option_value(wk, current_project(wk), "bindir", &install_dir); break; case tgt_static_library: get_option_value(wk, current_project(wk), "libdir", &install_dir); break; case tgt_dynamic_library: case tgt_shared_module: { enum machine_system sys = machine_system(); if (sys == machine_system_windows || sys == machine_system_cygwin) { get_option_value(wk, current_project(wk), "bindir", &install_dir); } else { get_option_value(wk, current_project(wk), "libdir", &install_dir); } break; } default: assert(false && "unreachable"); break; } } SBUF(install_src); path_join(wk, &install_src, get_cstr(wk, tgt->build_dir), get_cstr(wk, tgt->build_name)); SBUF(install_dest); path_join(wk, &install_dest, get_cstr(wk, install_dir), get_cstr(wk, tgt->build_name)); struct obj_install_target *install_tgt; if (!(install_tgt = push_install_target(wk, sbuf_into_str(wk, &install_src), sbuf_into_str(wk, &install_dest), akw[bt_kw_install_mode].val))) { return false; } install_tgt->build_target = true; if (soname_install) { push_install_target_install_dir(wk, soname_install, install_dir, akw[bt_kw_install_mode].val); } if (plain_name_install) { push_install_target_install_dir(wk, plain_name_install, install_dir, akw[bt_kw_install_mode].val); } } if (!build_tgt_determine_linker(wk, an[0].node, tgt)) { return false; } { // rpaths if (akw[bt_kw_build_rpath].set) { obj_array_push(wk, tgt->dep_internal.rpath, akw[bt_kw_build_rpath].val); } if (akw[bt_kw_install_rpath].set) { obj_array_push(wk, tgt->dep_internal.rpath, akw[bt_kw_install_rpath].val); } } tgt->dep = (struct build_dep) { .link_language = tgt->dep_internal.link_language, .include_directories = tgt->dep_internal.include_directories, .order_deps = tgt->dep_internal.order_deps, .rpath = tgt->dep_internal.rpath, .raw = tgt->dep_internal.raw, }; if (tgt->type == tgt_static_library) { tgt->dep.link_whole = tgt->dep_internal.link_whole; tgt->dep.link_with = tgt->dep_internal.link_with; tgt->dep.link_with_not_found = tgt->dep_internal.link_with_not_found; obj_array_dup(wk, tgt->dep_internal.link_args, &tgt->dep.link_args); } if (akw[bt_kw_link_args].set) { obj_array_extend(wk, tgt->dep_internal.link_args, akw[bt_kw_link_args].val); } L("adding build target %s", get_cstr(wk, tgt->build_name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } static bool typecheck_string_or_empty_array(struct workspace *wk, struct args_kw *kw) { if (!kw->set) { return true; } enum obj_type t = get_obj_type(wk, kw->val); if (t == obj_string) { return true; } else if (t == obj_array && get_obj_array(wk, kw->val)->len == 0) { kw->set = false; kw->val = 0; return true; } else { interp_error(wk, kw->node, "expected string or [], got %s", obj_type_to_s(t)); return false; } } static bool tgt_common(struct workspace *wk, uint32_t args_node, obj *res, enum tgt_type type, enum tgt_type argtype, bool tgt_type_from_kw) { struct args_norm an[] = { { obj_string }, { ARG_TYPE_GLOB | tc_coercible_files | tc_generated_list }, ARG_TYPE_NULL }; struct args_kw akw[] = { [bt_kw_sources] = { "sources", ARG_TYPE_ARRAY_OF | tc_coercible_files | tc_generated_list }, [bt_kw_include_directories] = { "include_directories", ARG_TYPE_ARRAY_OF | tc_coercible_inc }, [bt_kw_implicit_include_directories] = { "implicit_include_directories", obj_bool }, [bt_kw_dependencies] = { "dependencies", ARG_TYPE_ARRAY_OF | tc_dependency }, [bt_kw_install] = { "install", obj_bool }, [bt_kw_install_dir] = { "install_dir", obj_string }, [bt_kw_install_mode] = { "install_mode", tc_install_mode_kw }, [bt_kw_install_tag] = { "install_tag", tc_string }, // TODO [bt_kw_link_with] = { "link_with", tc_link_with_kw }, [bt_kw_link_whole] = { "link_whole", tc_link_with_kw }, [bt_kw_version] = { "version", obj_string }, [bt_kw_build_by_default] = { "build_by_default", obj_bool }, [bt_kw_extra_files] = { "extra_files", ARG_TYPE_ARRAY_OF | tc_coercible_files }, // ignored [bt_kw_target_type] = { "target_type", obj_string }, [bt_kw_name_prefix] = { "name_prefix", tc_string | tc_array }, [bt_kw_name_suffix] = { "name_suffix", tc_string | tc_array }, [bt_kw_soversion] = { "soversion", tc_number | tc_string }, [bt_kw_link_depends] = { "link_depends", ARG_TYPE_ARRAY_OF | tc_string | tc_file | tc_custom_target }, [bt_kw_objects] = { "objects", ARG_TYPE_ARRAY_OF | tc_file | tc_string }, [bt_kw_pic] = { "pic", obj_bool }, [bt_kw_pie] = { "pie", obj_bool }, [bt_kw_build_rpath] = { "build_rpath", obj_string }, [bt_kw_install_rpath] = { "install_rpath", obj_string }, [bt_kw_export_dynamic] = { "export_dynamic", obj_bool }, [bt_kw_vs_module_defs] = { "vs_module_defs", tc_string | tc_file | tc_custom_target }, [bt_kw_gnu_symbol_visibility] = { "gnu_symbol_visibility", obj_string }, [bt_kw_native] = { "native", obj_bool }, [bt_kw_darwin_versions] = { "darwin_versions", ARG_TYPE_ARRAY_OF | tc_string | tc_number }, [bt_kw_implib] = { "implib", obj_bool }, [bt_kw_gui_app] = { "gui_app", obj_bool }, [bt_kw_link_language] = { "link_language", obj_string }, [bt_kw_win_subsystem] = { "win_subsystem", obj_string }, [bt_kw_override_options] = { "override_options", ARG_TYPE_ARRAY_OF | obj_string }, /* lang args */ [bt_kw_c_pch] = { "c_pch", tc_string | tc_file, }, [bt_kw_cpp_pch] = { "cpp_pch", tc_string | tc_file, }, [bt_kw_c_args] = { "c_args", ARG_TYPE_ARRAY_OF | obj_string }, [bt_kw_cpp_args] = { "cpp_args", ARG_TYPE_ARRAY_OF | obj_string }, [bt_kw_objc_args] = { "objc_args", ARG_TYPE_ARRAY_OF | obj_string }, [bt_kw_nasm_args] = { "nasm_args", ARG_TYPE_ARRAY_OF | obj_string }, [bt_kw_masm_args] = { "masm_args", ARG_TYPE_ARRAY_OF | obj_string }, [bt_kw_link_args] = { "link_args", ARG_TYPE_ARRAY_OF | obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (tgt_type_from_kw) { if (!akw[bt_kw_target_type].set) { interp_error(wk, args_node, "missing required kwarg: %s", akw[bt_kw_target_type].key); return false; } if (!type_from_kw(wk, akw[bt_kw_target_type].node, akw[bt_kw_target_type].val, &type)) { return false; } } else { if (akw[bt_kw_target_type].set) { interp_error(wk, akw[bt_kw_target_type].node, "invalid kwarg"); return false; } } static const enum tgt_type keyword_validity[bt_kwargs_count] = { [bt_kw_version] = tgt_dynamic_library, [bt_kw_soversion] = tgt_dynamic_library, [bt_kw_vs_module_defs] = tgt_dynamic_library | tgt_static_library | tgt_shared_module, }; uint32_t i; for (i = 0; i < bt_kwargs_count; ++i) { if (keyword_validity[i] && akw[i].set && !(keyword_validity[i] & argtype)) { interp_error(wk, akw[i].node, "invalid kwarg"); return false; } } if (!typecheck_string_or_empty_array(wk, &akw[bt_kw_name_suffix])) { return false; } else if (!typecheck_string_or_empty_array(wk, &akw[bt_kw_name_prefix])) { return false; } if (type == (tgt_static_library | tgt_dynamic_library) && !akw[bt_kw_pic].set) { make_obj(wk, &akw[bt_kw_pic].val, obj_bool); set_obj_bool(wk, akw[bt_kw_pic].val, true); akw[bt_kw_pic].set = true; } bool multi_target = false; obj tgt = 0; for (i = 0; i <= tgt_type_count; ++i) { enum tgt_type t = 1 << i; if (!(type & t)) { continue; } if (tgt && !multi_target) { multi_target = true; make_obj(wk, res, obj_array); obj_array_push(wk, *res, tgt); // If this target is a multi-target (both_libraries), // set the objects argument with objects from the // previous target obj objects; if (!build_target_extract_all_objects(wk, an[0].node, tgt, &objects, true)) { return false; } if (akw[bt_kw_objects].set) { obj_array_extend(wk, akw[bt_kw_objects].val, objects); } else { akw[bt_kw_objects].set = true; akw[bt_kw_objects].val = objects; akw[bt_kw_objects].node = an[0].node; } } if (!create_target(wk, an, akw, t, multi_target, &tgt)) { return false; } if (multi_target) { obj_array_push(wk, *res, tgt); } else { *res = tgt; } } if (multi_target) { obj val; make_obj(wk, &val, obj_both_libs); struct obj_both_libs *both = get_obj_both_libs(wk, val); obj_array_index(wk, *res, 0, &both->static_lib); obj_array_index(wk, *res, 1, &both->dynamic_lib); *res = val; assert(get_obj_build_target(wk, both->static_lib)->type == tgt_static_library); assert(get_obj_build_target(wk, both->dynamic_lib)->type == tgt_dynamic_library); } return true; } bool func_executable(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, tgt_executable, tgt_executable, false); } bool func_static_library(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, tgt_static_library, tgt_static_library, false); } bool func_shared_library(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, tgt_dynamic_library, tgt_dynamic_library, false); } bool func_both_libraries(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, tgt_static_library | tgt_dynamic_library, tgt_static_library | tgt_dynamic_library, false); } bool func_library(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, get_option_default_library(wk), tgt_static_library | tgt_dynamic_library, false); } bool func_shared_module(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, tgt_shared_module, tgt_shared_module, false); } bool func_build_target(struct workspace *wk, obj _, uint32_t args_node, obj *res) { return tgt_common(wk, args_node, res, 0, tgt_executable | tgt_static_library | tgt_dynamic_library, true); } muon-0.2.0/src/functions/kernel/configure_file.c000066400000000000000000000460201441402162300216710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "buf_size.h" #include "coerce.h" #include "error.h" #include "functions/common.h" #include "functions/environment.h" #include "functions/kernel/configure_file.h" #include "functions/kernel/custom_target.h" #include "install.h" #include "lang/interpreter.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "platform/run_cmd.h" enum configure_file_output_format { configure_file_output_format_c, configure_file_output_format_nasm, }; static bool file_exists_with_content(struct workspace *wk, const char *dest, const char *out_buf, uint32_t out_len) { if (fs_file_exists(dest)) { struct source src = { 0 }; if (fs_read_entire_file(dest, &src)) { bool eql = out_len == src.len && memcmp(out_buf, src.src, src.len) == 0; fs_source_destroy(&src); return eql; } } return false; } static void configure_file_skip_whitespace(const struct source *src, uint32_t *i) { while (src->src[*i] && strchr(" \t", src->src[*i])) { ++(*i); } } static uint32_t configure_var_len(const char *p) { uint32_t i = 0; // Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define while (p[i] && (('a' <= p[i] && p[i] <= 'z') || ('A' <= p[i] && p[i] <= 'Z') || ('0' <= p[i] && p[i] <= '9') || '_' == p[i] || '-' == p[i] )) { ++i; } return i; } enum configure_file_syntax { configure_file_syntax_mesondefine = 0 << 0, configure_file_syntax_cmakedefine = 1 << 0, configure_file_syntax_mesonvar = 0 << 1, configure_file_syntax_cmakevar = 1 << 1, }; static bool substitute_config(struct workspace *wk, uint32_t dict, uint32_t in_node, const char *in, obj out, enum configure_file_syntax syntax) { const char *define; if (syntax & configure_file_syntax_cmakedefine) { define = "#cmakedefine "; } else { define = "#mesondefine "; } const char *varstart; char varend; if (syntax & configure_file_syntax_cmakevar) { varstart = "${"; varend = '}'; } else { varstart = "@"; varend = '@'; } const uint32_t define_len = strlen(define), varstart_len = strlen(varstart); bool ret = true; struct source src; if (!fs_read_entire_file(in, &src)) { ret = false; goto cleanup; } SBUF_manual(out_buf); uint32_t i, id_start, id_len, line = 1, start_of_line = 0, id_start_col = 0, id_start_line = 0; obj elem; char tmp_buf[BUF_SIZE_1k] = { 0 }; for (i = 0; i < src.len; ++i) { if (src.src[i] == '\n') { start_of_line = i + 1; ++line; } if (i == start_of_line && strncmp(&src.src[i], define, define_len) == 0) { i += define_len; configure_file_skip_whitespace(&src, &i); id_start = i; id_start_line = line; id_start_col = i - start_of_line + 1; id_len = configure_var_len(&src.src[id_start]); i += id_len; const char *sub = NULL, *deftype = "#define"; configure_file_skip_whitespace(&src, &i); if (!(src.src[i] == '\n' || src.src[i] == 0)) { if (syntax & configure_file_syntax_cmakedefine) { if (src.src[i] == '@' && strncmp(&src.src[i + 1], &src.src[id_start], id_len) == 0 && src.src[i + 1 + id_len] == '@') { i += 2 + id_len; configure_file_skip_whitespace(&src, &i); if (!(src.src[i] == '\n' || src.src[i] == 0)) { goto extraneous_cmake_chars; } } else { extraneous_cmake_chars: error_messagef(&src, id_start_line, i - start_of_line + 1, log_error, "cmakedefine only supports two modes:\n" "\t--> #cmakedefine %.*s\n " "\t--> #cmakedefine %.*s @%.*s@\n" "In particular, no value other than \"%.*s\" will be substituted and\n" "no extra characters (comments, etc.) are allowed on the line.", id_len, &src.src[id_start], id_len, &src.src[id_start], id_len, &src.src[id_start], id_len, &src.src[id_start] ); return false; } } else { error_messagef(&src, id_start_line, i - start_of_line + 1, log_error, "expected exactly one token on mesondefine line"); return false; } } if (i == id_start) { error_messagef(&src, id_start_line, id_start_col, log_error, "key of zero length not supported"); return false; } else if (!obj_dict_index_strn(wk, dict, &src.src[id_start], id_len, &elem)) { deftype = "/* undef"; sub = "*/"; goto write_mesondefine; } switch (get_obj_type(wk, elem)) { case obj_bool: { if (!get_obj_bool(wk, elem)) { deftype = "#undef"; } break; } case obj_string: { sub = get_cstr(wk, elem); break; } case obj_number: snprintf(tmp_buf, BUF_SIZE_1k, "%" PRId64, get_obj_number(wk, elem)); sub = tmp_buf; break; default: error_messagef(&src, id_start_line, id_start_col, log_error, "invalid type for %s: '%s'", define, obj_type_to_s(get_obj_type(wk, elem))); return false; } write_mesondefine: sbuf_pushn(wk, &out_buf, deftype, strlen(deftype)); sbuf_pushn(wk, &out_buf, " ", 1); sbuf_pushn(wk, &out_buf, &src.src[id_start], id_len); if (sub) { sbuf_pushn(wk, &out_buf, " ", 1); sbuf_pushn(wk, &out_buf, sub, strlen(sub)); } i -= 1; // so we catch the newline } else if (src.src[i] == '\\') { /* cope with weird config file escaping rules :( * * - Backslashes not directly preceeding a format character are not modified. * - The number of backslashes preceding varstart in the * output is equal to the number of backslashes in * the input divided by two, rounding down. */ uint32_t j, output_backslashes; bool output_format_char = false; for (j = 1; src.src[i + j] && src.src[i + j] == '\\'; ++j) { } if (strncmp(&src.src[i + j], varstart, varstart_len) == 0) { output_backslashes = j / 2; if ((j & 1) != 0) { output_format_char = true; i += j; } else { i += j - 1; } } else { i += j - 1; output_backslashes = j; } for (j = 0; j < output_backslashes; ++j) { sbuf_pushn(wk, &out_buf, "\\", 1); } if (output_format_char) { sbuf_pushn(wk, &out_buf, varstart, varstart_len); i += varstart_len - 1; } } else if (strncmp(&src.src[i], varstart, varstart_len) == 0) { i += varstart_len; id_start_line = line; id_start = i; id_start_col = id_start - start_of_line + 1; i += configure_var_len(&src.src[id_start]); if (src.src[i] != varend) { i = id_start - 1; sbuf_pushn(wk, &out_buf, varstart, varstart_len); continue; } if (i <= id_start) { error_messagef(&src, id_start_line, id_start_col, log_error, "key of zero length not supported"); return false; } else if (!obj_dict_index_strn(wk, dict, &src.src[id_start], i - id_start, &elem)) { error_messagef(&src, id_start_line, id_start_col, log_error, "key not found in configuration data"); return false; } obj sub; if (!coerce_string(wk, in_node, elem, &sub)) { error_messagef(&src, id_start_line, id_start_col, log_error, "unable to substitute value"); return false; } const struct str *ss = get_str(wk, sub); sbuf_pushn(wk, &out_buf, ss->s, ss->len); } else { sbuf_pushn(wk, &out_buf, &src.src[i], 1); } } if (file_exists_with_content(wk, get_cstr(wk, out), out_buf.buf, out_buf.len)) { goto cleanup; } if (!fs_write(get_cstr(wk, out), (uint8_t *)out_buf.buf, out_buf.len)) { ret = false; goto cleanup; } if (!fs_copy_metadata(in, get_cstr(wk, out))) { ret = false; goto cleanup; } cleanup: fs_source_destroy(&src); sbuf_destroy(&out_buf); return ret; } struct generate_config_ctx { struct sbuf *out_buf; uint32_t node; enum configure_file_output_format output_format; }; static enum iteration_result generate_config_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct generate_config_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, val); char define_prefix = (char[]){ [configure_file_output_format_c] = '#', [configure_file_output_format_nasm] = '%', }[ctx->output_format]; switch (t) { case obj_string: /* conf_data.set('FOO', '"string"') => #define FOO "string" */ /* conf_data.set('FOO', 'a_token') => #define FOO a_token */ sbuf_pushf(wk, ctx->out_buf, "%cdefine %s %s\n", define_prefix, get_cstr(wk, key), get_cstr(wk, val)); break; case obj_bool: /* conf_data.set('FOO', true) => #define FOO */ /* conf_data.set('FOO', false) => #undef FOO */ if (get_obj_bool(wk, val)) { sbuf_pushf(wk, ctx->out_buf, "%cdefine %s\n", define_prefix, get_cstr(wk, key)); } else { sbuf_pushf(wk, ctx->out_buf, "%cundef %s\n", define_prefix, get_cstr(wk, key)); } break; case obj_number: /* conf_data.set('FOO', 1) => #define FOO 1 */ /* conf_data.set('FOO', 0) => #define FOO 0 */ sbuf_pushf(wk, ctx->out_buf, "%cdefine %s %" PRId64 "\n", define_prefix, get_cstr(wk, key), get_obj_number(wk, val)); break; default: interp_error(wk, ctx->node, "invalid type for config data value: '%s'", obj_type_to_s(t)); return ir_err; } return ir_cont; } static bool generate_config(struct workspace *wk, enum configure_file_output_format format, obj dict, uint32_t node, obj out_path) { SBUF_manual(out_buf); struct generate_config_ctx ctx = { .out_buf = &out_buf, .node = node, .output_format = format, }; bool ret; ret = obj_dict_foreach(wk, dict, &ctx, generate_config_iter); if (!file_exists_with_content(wk, get_cstr(wk, out_path), ctx.out_buf->buf, ctx.out_buf->len)) { if (!fs_write(get_cstr(wk, out_path), (uint8_t *)ctx.out_buf->buf, ctx.out_buf->len)) { ret = false; } } sbuf_destroy(&out_buf); return ret; } static bool configure_file_with_command(struct workspace *wk, uint32_t node, obj command, obj input, obj out_path, obj depfile, bool capture) { obj args, output_arr; { obj f; make_obj(wk, &f, obj_file); *get_obj_file(wk, f) = out_path; make_obj(wk, &output_arr, obj_array); obj_array_push(wk, output_arr, f); } { // XXX: depfile for configure_file is not supported, this is // only here to make the types align obj f; make_obj(wk, &f, obj_file); *get_obj_file(wk, f) = depfile; depfile = f; } struct process_custom_target_commandline_opts opts = { .err_node = node, .input = input, .output = output_arr, .depfile = depfile, }; make_obj(wk, &opts.depends, obj_array); if (!process_custom_target_commandline(wk, &opts, command, &args)) { return false; } bool ret = false; struct run_cmd_ctx cmd_ctx = { 0 }; const char *argstr, *envstr; uint32_t argc, envc; if (!path_chdir(get_cstr(wk, current_project(wk)->build_dir))) { return false; } obj env; make_obj(wk, &env, obj_dict); set_default_environment_vars(wk, env, true); join_args_argstr(wk, &argstr, &argc, args); env_to_envstr(wk, &envstr, &envc, env); if (!run_cmd(&cmd_ctx, argstr, argc, envstr, envc)) { interp_error(wk, node, "error running command: %s", cmd_ctx.err_msg); goto ret; } if (cmd_ctx.status != 0) { interp_error(wk, node, "error running command: %s", cmd_ctx.err.buf); goto ret; } if (capture) { if (file_exists_with_content(wk, get_cstr(wk, out_path), cmd_ctx.out.buf, cmd_ctx.out.len)) { ret = true; } else { ret = fs_write(get_cstr(wk, out_path), (uint8_t *)cmd_ctx.out.buf, cmd_ctx.out.len); } } else { ret = true; } ret: if (!path_chdir(wk->source_root)) { return false; } run_cmd_ctx_destroy(&cmd_ctx); return ret; } static bool array_to_elem_or_err(struct workspace *wk, uint32_t node, uint32_t arr, uint32_t *res) { if (!typecheck(wk, node, arr, obj_array)) { return false; } if (get_obj_array(wk, arr)->len != 1) { interp_error(wk, node, "expected an array of length 1"); return false; } obj_array_index(wk, arr, 0, res); return true; } static bool is_substr(const char *s, const char *sub, uint32_t *len) { *len = strlen(sub); return strncmp(s, sub, *len) == 0; } static bool perform_output_string_substitutions(struct workspace *wk, uint32_t node, uint32_t src, uint32_t input_arr, uint32_t *res) { const char *s = get_cstr(wk, src); uint32_t len; obj str = make_str(wk, ""), e = 0; for (; *s; ++s) { if (is_substr(s, "@BASENAME@", &len)) { if (!array_to_elem_or_err(wk, node, input_arr, &e)) { return false; } assert(e); SBUF(buf); char *c; path_basename(wk, &buf, get_file_path(wk, e)); if ((c = strrchr(buf.buf, '.'))) { *c = 0; buf.len = strlen(buf.buf); } str_app(wk, str, buf.buf); s += len - 1; } else if (is_substr(s, "@PLAINNAME@", &len)) { if (!array_to_elem_or_err(wk, node, input_arr, &e)) { return false; } SBUF(buf); path_basename(wk, &buf, get_file_path(wk, e)); str_app(wk, str, buf.buf); s += len - 1; } else { str_appn(wk, str, s, 1); } } *res = str; return true; } static bool exclusive_or(bool *vals, uint32_t len) { uint32_t i; bool found = false; for (i = 0; i < len; ++i) { if (vals[i]) { if (found) { return false; } else { found = true; } } } return found; } bool func_configure_file(struct workspace *wk, obj _, uint32_t args_node, obj *res) { obj input_arr = 0, output_str; enum kwargs { kw_configuration, kw_input, kw_output, kw_command, kw_capture, kw_install, kw_install_dir, kw_install_mode, kw_install_tag, kw_copy, kw_format, kw_output_format, kw_encoding, // TODO: ignored kw_depfile, // TODO: ignored }; struct args_kw akw[] = { [kw_configuration] = { "configuration", tc_configuration_data | tc_dict }, [kw_input] = { "input", ARG_TYPE_ARRAY_OF | tc_coercible_files, }, [kw_output] = { "output", obj_string, .required = true }, [kw_command] = { "command", obj_array }, [kw_capture] = { "capture", obj_bool }, [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_copy] = { "copy", obj_bool }, [kw_format] = { "format", obj_string }, [kw_output_format] = { "output_format", obj_string }, [kw_encoding] = { "encoding", obj_string }, [kw_depfile] = { "depfile", obj_string }, 0 }; if (!interp_args(wk, args_node, NULL, NULL, akw)) { return false; } enum configure_file_output_format output_format = configure_file_output_format_c; if (akw[kw_output_format].set) { const struct str *output_format_str = get_str(wk, akw[kw_output_format].val); if (str_eql(output_format_str, &WKSTR("c"))) { output_format = configure_file_output_format_c; } else if (str_eql(output_format_str, &WKSTR("nasm"))) { output_format = configure_file_output_format_nasm; } else { interp_error(wk, akw[kw_output_format].node, "invalid output format %o", akw[kw_output_format].val); return false; } } if (akw[kw_input].set) { if (!coerce_files(wk, akw[kw_input].node, akw[kw_input].val, &input_arr)) { return false; } } else { // set this so we can use it in error handling later akw[kw_input].node = args_node; } { /* setup out file */ obj subd; if (!perform_output_string_substitutions(wk, akw[kw_output].node, akw[kw_output].val, input_arr, &subd)) { return false; } const char *out = get_cstr(wk, subd); SBUF(out_path); if (!path_is_basename(out)) { interp_error(wk, akw[kw_output].node, "config file output '%s' contains path separator", out); return false; } if (!fs_mkdir_p(get_cstr(wk, current_project(wk)->build_dir))) { return false; } path_join(wk, &out_path, get_cstr(wk, current_project(wk)->build_dir), out); LOG_I("configuring '%s'", out_path.buf); output_str = sbuf_into_str(wk, &out_path); make_obj(wk, res, obj_file); *get_obj_file(wk, *res) = output_str; } if (!exclusive_or((bool []) { akw[kw_command].set, akw[kw_configuration].set, akw[kw_copy].set }, 3)) { interp_error(wk, args_node, "you must pass either command:, configuration:, or copy:"); return false; } if (akw[kw_command].set) { bool capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val); if (!configure_file_with_command(wk, akw[kw_command].node, akw[kw_command].val, input_arr, output_str, akw[kw_depfile].val, capture)) { return false; } } else if (akw[kw_copy].set) { obj input; bool copy_res = false; if (!array_to_elem_or_err(wk, akw[kw_input].node, input_arr, &input)) { return false; } workspace_add_regenerate_deps(wk, *get_obj_file(wk, input)); struct source src = { 0 }; if (!fs_read_entire_file(get_file_path(wk, input), &src)) { return false; } if (!file_exists_with_content(wk, get_cstr(wk, output_str), src.src, src.len)) { if (!fs_write(get_cstr(wk, output_str), (uint8_t *)src.src, src.len)) { goto copy_err; } if (!fs_copy_metadata(get_file_path(wk, input), get_cstr(wk, output_str))) { goto copy_err; } } copy_res = true; copy_err: fs_source_destroy(&src); if (!copy_res) { return false; } } else { obj dict, conf = akw[kw_configuration].val; enum obj_type t = get_obj_type(wk, conf); switch (t) { case obj_dict: dict = conf; break; case obj_configuration_data: dict = get_obj_configuration_data(wk, conf)->dict; break; default: interp_error(wk, akw[kw_configuration].node, "invalid type for configuration data '%s'", obj_type_to_s(t)); return false; } if (akw[kw_input].set) { obj input; /* NOTE: when meson gets an empty array as the input argument * to configure file, it acts like the input keyword wasn't set. * We throw an error. */ if (!array_to_elem_or_err(wk, akw[kw_input].node, input_arr, &input)) { return false; } workspace_add_regenerate_deps(wk, *get_obj_file(wk, input)); const char *path = get_file_path(wk, input); enum configure_file_syntax syntax = configure_file_syntax_mesondefine | configure_file_syntax_mesonvar; if (akw[kw_format].set) { const struct str *fmt = get_str(wk, akw[kw_format].val); if (str_eql(fmt, &WKSTR("meson"))) { syntax = configure_file_syntax_mesondefine | configure_file_syntax_mesonvar; } else if (str_eql(fmt, &WKSTR("cmake"))) { syntax = configure_file_syntax_cmakedefine | configure_file_syntax_cmakevar; } else if (str_eql(fmt, &WKSTR("cmake@"))) { syntax = configure_file_syntax_cmakedefine | configure_file_syntax_mesonvar; } else { interp_error(wk, akw[kw_format].node, "invalid format type %o", akw[kw_format].val); return false; } } if (!substitute_config(wk, dict, akw[kw_input].node, path, output_str, syntax)) { return false; } } else { if (!generate_config(wk, output_format, dict, akw[kw_configuration].node, output_str)) { return false; } } } if ((akw[kw_install].set && get_obj_bool(wk, akw[kw_install].val)) || (!akw[kw_install].set && akw[kw_install_dir].set)) { if (!akw[kw_install_dir].set) { interp_error(wk, akw[kw_install].node, "configure_file installation requires install_dir"); return false; } push_install_target_install_dir(wk, output_str, akw[kw_install_dir].val, akw[kw_install_mode].val); } return true; } muon-0.2.0/src/functions/kernel/custom_target.c000066400000000000000000000540031441402162300215710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: dffdff2423 * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "coerce.h" #include "error.h" #include "functions/generator.h" #include "functions/kernel/custom_target.h" #include "functions/string.h" #include "install.h" #include "lang/interpreter.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" struct custom_target_cmd_fmt_ctx { struct process_custom_target_commandline_opts *opts; uint32_t i; obj *res; bool skip_depends; }; static bool prefix_plus_index(const struct str *ss, const char *prefix, int64_t *index) { uint32_t len = strlen(prefix); if (str_startswith(ss, &WKSTR(prefix))) { return str_to_i(&(struct str) { .s = &ss->s[len], .len = ss->len - len }, index); } return false; } static void str_relative_to_build_root(struct workspace *wk, struct custom_target_cmd_fmt_ctx *ctx, const char *path_orig, obj *res) { SBUF(rel); const char *path = path_orig; if (!ctx->opts->relativize) { *res = make_str(wk, path); return; } if (!path_is_absolute(path)) { *res = make_str(wk, path); return; } path_relative_to(wk, &rel, wk->build_root, path); if (ctx->i == 0) { // prefix relative argv0 with ./ so that executables are looked // up properly if they reside in the build root. Without this, // an executable in the build root will be called without any // path elements, and will be assumed to be on PATH, which // either results in the wrong executable being run, or a // command not found error. SBUF(exe); path_executable(wk, &exe, rel.buf); *res = sbuf_into_str(wk, &exe); } else { *res = sbuf_into_str(wk, &rel); } } static enum format_cb_result format_cmd_arg_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *strkey, obj *elem) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; enum cmd_arg_fmt_key { key_input, key_output, key_outdir, key_depfile, key_plainname, key_basename, key_private_dir, key_source_root, key_build_root, key_build_dir, key_current_source_dir, cmd_arg_fmt_key_count, }; const struct { char *key; bool valid; bool needs_name; } key_names[cmd_arg_fmt_key_count] = { [key_input ] = { "INPUT", ctx->opts->input }, [key_output ] = { "OUTPUT", ctx->opts->output }, [key_outdir ] = { "OUTDIR", ctx->opts->output }, [key_depfile ] = { "DEPFILE", ctx->opts->depfile }, [key_plainname ] = { "PLAINNAME", ctx->opts->input }, [key_basename ] = { "BASENAME", ctx->opts->input }, [key_private_dir ] = { "PRIVATE_DIR", ctx->opts->output, true, }, [key_source_root ] = { "SOURCE_ROOT", true }, [key_build_root ] = { "BUILD_ROOT", true }, [key_build_dir ] = { "BUILD_DIR", ctx->opts->build_dir }, [key_current_source_dir] = { "CURRENT_SOURCE_DIR", true }, }; enum cmd_arg_fmt_key key; for (key = 0; key < cmd_arg_fmt_key_count; ++key) { if (!str_eql(strkey, &WKSTR(key_names[key].key))) { continue; } if (!key_names[key].valid || (key_names[key].needs_name && !ctx->opts->name)) { return format_cb_not_found; } break; } obj e; switch (key) { case key_input: case key_output: { obj arr = key == key_input ? ctx->opts->input : ctx->opts->output; int64_t index = 0; if (!boundscheck(wk, ctx->opts->err_node, get_obj_array(wk, arr)->len, &index)) { return format_cb_error; } obj_array_index(wk, arr, 0, &e); str_relative_to_build_root(wk, ctx, get_file_path(wk, e), elem); return format_cb_found; } case key_outdir: /* @OUTDIR@: the full path to the directory where the output(s) * must be written */ str_relative_to_build_root(wk, ctx, get_cstr(wk, current_project(wk)->build_dir), elem); return format_cb_found; case key_current_source_dir: /* @CURRENT_SOURCE_DIR@: this is the directory where the * currently processed meson.build is located in. Depending on * the backend, this may be an absolute or a relative to * current workdir path. */ str_relative_to_build_root(wk, ctx, get_cstr(wk, current_project(wk)->cwd), elem); return format_cb_found; case key_private_dir: { /* @PRIVATE_DIR@ (since 0.50.1): path to a directory where the * custom target must store all its intermediate files. */ SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, ctx->opts->name)); sbuf_pushs(wk, &path, ".p"); str_relative_to_build_root(wk, ctx, path.buf, elem); return format_cb_found; } case key_depfile: /* @DEPFILE@: the full path to the dependency file passed to * depfile */ str_relative_to_build_root(wk, ctx, get_file_path(wk, ctx->opts->depfile), elem); return format_cb_found; case key_source_root: /* @SOURCE_ROOT@: the path to the root of the source tree. * Depending on the backend, this may be an absolute or a * relative to current workdir path. */ str_relative_to_build_root(wk, ctx, wk->source_root, elem); return format_cb_found; case key_build_root: /* @BUILD_ROOT@: the path to the root of the build tree. * Depending on the backend, this may be an absolute or a * relative to current workdir path. */ str_relative_to_build_root(wk, ctx, wk->build_root, elem); return format_cb_found; case key_build_dir: // only for generators str_relative_to_build_root(wk, ctx, ctx->opts->build_dir, elem); return format_cb_found; case key_plainname: /* @PLAINNAME@: the input filename, without a path */ case key_basename: { /* @BASENAME@: the input filename, with extension removed */ struct obj_array *in = get_obj_array(wk, ctx->opts->input); if (in->len != 1) { interp_error(wk, ctx->opts->err_node, "to use @PLAINNAME@ and @BASENAME@ in a custom " "target command, there must be exactly one input"); return format_cb_error; } obj in0; obj_array_index(wk, ctx->opts->input, 0, &in0); const struct str *orig_str = get_str(wk, *get_obj_file(wk, in0)); SBUF(plainname); path_basename(wk, &plainname, orig_str->s); if (key == key_basename) { SBUF(basename); path_without_ext(wk, &basename, plainname.buf); str_relative_to_build_root(wk, ctx, basename.buf, elem); } else { str_relative_to_build_root(wk, ctx, plainname.buf, elem); } return format_cb_found; } default: break; } int64_t index; obj arr; if (prefix_plus_index(strkey, "INPUT", &index)) { arr = ctx->opts->input; } else if (prefix_plus_index(strkey, "OUTPUT", &index)) { arr = ctx->opts->output; } else { if (ctx->opts->err_node) { interp_warning(wk, ctx->opts->err_node, "not substituting unknown key '%.*s' in commandline", strkey->len, strkey->s); } return format_cb_skip; } if (!boundscheck(wk, ctx->opts->err_node, get_obj_array(wk, arr)->len, &index)) { return format_cb_error; } obj_array_index(wk, arr, index, &e); str_relative_to_build_root(wk, ctx, get_file_path(wk, e), elem); return format_cb_found; } static enum iteration_result custom_target_cmd_fmt_iter(struct workspace *wk, void *_ctx, obj val) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; obj ss; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_both_libs: case obj_build_target: case obj_external_program: case obj_file: { obj str; if (!coerce_executable(wk, ctx->opts->err_node, val, &str)) { return ir_err; } str_relative_to_build_root(wk, ctx, get_cstr(wk, str), &ss); if (!ctx->skip_depends) { obj_array_push(wk, ctx->opts->depends, ss); } break; } case obj_string: { if (ctx->opts->input && str_eql(get_str(wk, val), &WKSTR("@INPUT@"))) { ctx->skip_depends = true; if (!obj_array_foreach(wk, ctx->opts->input, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } ctx->skip_depends = false; return ir_cont; } else if (ctx->opts->output && str_eql(get_str(wk, val), &WKSTR("@OUTPUT@"))) { ctx->skip_depends = true; if (!obj_array_foreach(wk, ctx->opts->output, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } ctx->skip_depends = false; goto cont; } else if (ctx->opts->extra_args_valid && str_eql(get_str(wk, val), &WKSTR("@EXTRA_ARGS@"))) { if (ctx->opts->extra_args) { obj_array_extend(wk, *ctx->res, ctx->opts->extra_args); ctx->opts->extra_args_used = true; } goto cont; } obj s; if (!string_format(wk, ctx->opts->err_node, val, &s, ctx, format_cmd_arg_cb)) { return ir_err; } ss = s; break; } case obj_custom_target: { obj output = get_obj_custom_target(wk, val)->output; if (!obj_array_foreach(wk, output, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } goto cont; } case obj_compiler: { obj cmd_array = get_obj_compiler(wk, val)->cmd_arr; if (!obj_array_foreach(wk, cmd_array, ctx, custom_target_cmd_fmt_iter)) { return ir_err; } goto cont; } default: interp_error(wk, ctx->opts->err_node, "unable to coerce %o to string", val); return ir_err; } assert(get_obj_type(wk, ss) == obj_string); obj_array_push(wk, *ctx->res, ss); cont: ++ctx->i; return ir_cont; } bool process_custom_target_commandline(struct workspace *wk, struct process_custom_target_commandline_opts *opts, obj arr, obj *res) { make_obj(wk, res, obj_array); struct custom_target_cmd_fmt_ctx ctx = { .opts = opts, .res = res, }; if (!obj_array_foreach_flat(wk, arr, &ctx, custom_target_cmd_fmt_iter)) { return false; } if (!get_obj_array(wk, *res)->len) { interp_error(wk, opts->err_node, "cmd cannot be empty"); return false; } return true; } static enum format_cb_result format_cmd_output_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *strkey, obj *elem) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; enum cmd_output_fmt_key { key_plainname, key_basename, cmd_output_fmt_key_count }; const char *key_names[cmd_output_fmt_key_count] = { [key_plainname] = "PLAINNAME", [key_basename] = "BASENAME", }; enum cmd_output_fmt_key key; for (key = 0; key < cmd_output_fmt_key_count; ++key) { if (str_eql(strkey, &WKSTR(key_names[key]))) { break; } } if (key >= cmd_output_fmt_key_count) { return format_cb_not_found; } struct obj_array *in = get_obj_array(wk, ctx->opts->input); if (in->len != 1) { interp_error(wk, ctx->opts->err_node, "to use @PLAINNAME@ and @BASENAME@ in a custom " "target output, there must be exactly one input"); return format_cb_error; } obj in0; obj_array_index(wk, ctx->opts->input, 0, &in0); const struct str *ss = get_str(wk, *get_obj_file(wk, in0)); SBUF(buf); switch (key) { case key_plainname: path_basename(wk, &buf, ss->s); break; case key_basename: { SBUF(basename); path_basename(wk, &basename, ss->s); path_without_ext(wk, &buf, basename.buf); break; } default: assert(false && "unreachable"); return format_cb_error; } *elem = sbuf_into_str(wk, &buf); return format_cb_found; } static enum iteration_result custom_command_output_format_iter(struct workspace *wk, void *_ctx, obj v) { struct custom_target_cmd_fmt_ctx *ctx = _ctx; obj file = *get_obj_file(wk, v); obj s; if (!string_format(wk, ctx->opts->err_node, file, &s, ctx, format_cmd_output_cb)) { return ir_err; } obj f; make_obj(wk, &f, obj_file); *get_obj_file(wk, f) = s; obj_array_push(wk, ctx->opts->output, f); return ir_cont; } struct process_custom_tgt_sources_ctx { uint32_t err_node; obj tgt_id; obj res; }; static enum iteration_result process_custom_tgt_sources_iter(struct workspace *wk, void *_ctx, obj val) { obj res; struct process_custom_tgt_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: if (!generated_list_process_for_target(wk, ctx->err_node, val, ctx->tgt_id, true, &res)) { return ir_err; } break; default: { if (!coerce_files(wk, ctx->err_node, val, &res)) { return ir_err; } break; } } obj_array_extend_nodup(wk, ctx->res, res); return ir_cont; } bool make_custom_target(struct workspace *wk, struct make_custom_target_opts *opts, obj *res) { obj input, raw_output, output, args; make_obj(wk, res, obj_custom_target); struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->name = opts->name; // A custom_target won't have a name if it is from a generator if (opts->name) { /* private path */ SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->build_dir), get_cstr(wk, opts->name)); sbuf_pushs(wk, &path, ".p"); tgt->private_path = sbuf_into_str(wk, &path); } if (opts->input_orig) { make_obj(wk, &input, obj_array); struct process_custom_tgt_sources_ctx ctx = { .err_node = opts->input_node, .res = input, .tgt_id = *res, }; if (get_obj_type(wk, opts->input_orig) != obj_array) { obj arr_input; make_obj(wk, &arr_input, obj_array); obj_array_push(wk, arr_input, opts->input_orig); opts->input_orig = arr_input; } if (!obj_array_foreach_flat(wk, opts->input_orig, &ctx, process_custom_tgt_sources_iter)) { return false; } } else { input = 0; } if (opts->output_orig) { if (!coerce_output_files(wk, opts->output_node, opts->output_orig, opts->output_dir, &raw_output)) { return false; } else if (!get_obj_array(wk, raw_output)->len) { interp_error(wk, opts->output_node, "output cannot be empty"); return false; } make_obj(wk, &output, obj_array); struct custom_target_cmd_fmt_ctx ctx = { .opts = &(struct process_custom_target_commandline_opts) { .err_node = opts->output_node, .input = input, .output = output, .name = opts->name, }, }; if (!obj_array_foreach(wk, raw_output, &ctx, custom_command_output_format_iter)) { return false; } } else { output = 0; } obj depfile = 0; if (opts->depfile_orig) { obj raw_depfiles; if (!coerce_output_files(wk, 0, opts->depfile_orig, opts->output_dir, &raw_depfiles)) { return false; } if (!obj_array_flatten_one(wk, raw_depfiles, &depfile)) { UNREACHABLE; } struct custom_target_cmd_fmt_ctx ctx = { .opts = &(struct process_custom_target_commandline_opts) { .input = input, }, }; obj depfile_formatted; if (!string_format(wk, 0, *get_obj_file(wk, depfile), &depfile_formatted, &ctx, format_cmd_output_cb)) { return ir_err; } *get_obj_file(wk, depfile) = depfile_formatted; } struct process_custom_target_commandline_opts cmdline_opts = { .err_node = opts->command_node, .relativize = true, .name = opts->name, .input = input, .output = output, .depfile = depfile, .build_dir = opts->build_dir, .extra_args = opts->extra_args, .extra_args_valid = opts->extra_args_valid, }; make_obj(wk, &cmdline_opts.depends, obj_array); if (!process_custom_target_commandline(wk, &cmdline_opts, opts->command_orig, &args)) { return false; } if (opts->extra_args && !cmdline_opts.extra_args_used) { interp_warning(wk, opts->command_node, "extra args passed, but no @EXTRA_ARGS@ key found to substitute"); } if (opts->capture) { tgt->flags |= custom_target_capture; } if (opts->feed) { tgt->flags |= custom_target_feed; } tgt->args = args; tgt->input = input; tgt->output = output; tgt->depfile = depfile; tgt->depends = cmdline_opts.depends; return true; } bool func_custom_target(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_input, kw_output, kw_command, kw_capture, kw_install, kw_install_dir, kw_install_mode, kw_install_tag, kw_build_by_default, kw_depfile, kw_depend_files, kw_depends, kw_build_always_stale, kw_build_always, kw_env, kw_feed, kw_console, }; struct args_kw akw[] = { [kw_input] = { "input", ARG_TYPE_ARRAY_OF | tc_coercible_files | tc_generated_list, }, [kw_output] = { "output", ARG_TYPE_ARRAY_OF | tc_string, .required = true }, [kw_command] = { "command", tc_command_array | tc_both_libs, .required = true }, [kw_capture] = { "capture", obj_bool }, [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", ARG_TYPE_ARRAY_OF | tc_string | tc_bool }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", tc_string }, // TODO [kw_build_by_default] = { "build_by_default", obj_bool }, [kw_depfile] = { "depfile", obj_string }, [kw_depend_files] = { "depend_files", ARG_TYPE_ARRAY_OF | tc_string | tc_file }, [kw_depends] = { "depends", tc_depends_kw }, [kw_build_always_stale] = { "build_always_stale", obj_bool }, [kw_build_always] = { "build_always", obj_bool }, [kw_env] = { "env", tc_coercible_env }, [kw_feed] = { "feed", obj_bool }, [kw_console] = { "console", obj_bool }, 0 }; if (!interp_args(wk, args_node, NULL, ao, akw)) { return false; } obj name; if (ao[0].set) { name = ao[0].val; } else { if (!get_obj_array(wk, akw[kw_output].val)->len) { interp_error(wk, akw[kw_output].node, "output cannot be empty"); return false; } obj v; obj_array_index(wk, akw[kw_output].val, 0, &v); name = v; } struct make_custom_target_opts opts = { .name = name, .input_node = akw[kw_input].node, .output_node = akw[kw_output].node, .command_node = akw[kw_command].node, .input_orig = akw[kw_input].val, .output_orig = akw[kw_output].val, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = akw[kw_command].val, .depfile_orig = akw[kw_depfile].val, .capture = akw[kw_capture].set && get_obj_bool(wk, akw[kw_capture].val), .feed = akw[kw_feed].set && get_obj_bool(wk, akw[kw_feed].val), }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); if (akw[kw_depend_files].set) { obj depend_files; if (!coerce_files(wk, akw[kw_depend_files].node, akw[kw_depend_files].val, &depend_files)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depend_files); } if (akw[kw_depends].set) { obj depends; if (!coerce_files(wk, akw[kw_depends].node, akw[kw_depends].val, &depends)) { return false; } obj_array_extend_nodup(wk, tgt->depends, depends); } if (akw[kw_build_always_stale].set && get_obj_bool(wk, akw[kw_build_always_stale].val)) { tgt->flags |= custom_target_build_always_stale; } if (akw[kw_build_by_default].set && get_obj_bool(wk, akw[kw_build_by_default].val)) { tgt->flags |= custom_target_build_by_default; } if (akw[kw_build_always].set && get_obj_bool(wk, akw[kw_build_always].val)) { tgt->flags |= custom_target_build_always_stale | custom_target_build_by_default; } if (akw[kw_console].set && get_obj_bool(wk, akw[kw_console].val)) { if (opts.capture) { interp_error(wk, akw[kw_console].node, "console and capture cannot both be set to true"); return false; } tgt->flags |= custom_target_console; } if ((akw[kw_install].set && get_obj_bool(wk, akw[kw_install].val)) || (!akw[kw_install].set && akw[kw_install_dir].set)) { if (!akw[kw_install_dir].set || !get_obj_array(wk, akw[kw_install_dir].val)->len) { interp_error(wk, akw[kw_install].node, "custom target installation requires install_dir"); return false; } if (!akw[kw_build_by_default].set) { tgt->flags |= custom_target_build_by_default; } obj install_mode_id = 0; if (akw[kw_install_mode].set) { install_mode_id = akw[kw_install_mode].val; } obj install_dir = akw[kw_install_dir].val; if (get_obj_array(wk, akw[kw_install_dir].val)->len == 1) { obj i0; obj_array_index(wk, akw[kw_install_dir].val, 0, &i0); install_dir = i0; } if (!push_install_targets(wk, akw[kw_install_dir].node, tgt->output, install_dir, install_mode_id, false)) { return false; } } if (!coerce_environment_from_kwarg(wk, &akw[kw_env], false, &tgt->env)) { return false; } L("adding custom target '%s'", get_cstr(wk, tgt->name)); obj_array_push(wk, current_project(wk)->targets, *res); return true; } bool func_vcs_tag(struct workspace *wk, obj _, uint32_t args_node, obj *res) { enum kwargs { kw_input, kw_output, kw_command, kw_fallback, kw_replace_string, }; struct args_kw akw[] = { [kw_input] = { "input", ARG_TYPE_ARRAY_OF | tc_coercible_files, .required = true }, [kw_output] = { "output", obj_string, .required = true }, [kw_command] = { "command", tc_command_array | tc_both_libs }, [kw_fallback] = { "fallback", obj_string }, [kw_replace_string] = { "replace_string", obj_string }, 0 }; if (!interp_args(wk, args_node, NULL, NULL, akw)) { return false; } obj replace_string = akw[kw_replace_string].set ? akw[kw_replace_string].val : make_str(wk, "@VCS_TAG@"); obj fallback; if (akw[kw_fallback].set) { fallback = akw[kw_fallback].val; } else { fallback = current_project(wk)->cfg.version; } obj command; make_obj(wk, &command, obj_array); push_args_null_terminated(wk, command, (char *const []){ (char *)wk->argv0, "internal", "eval", "-e", "vcs_tagger.meson", NULL, }); obj input; { obj input_arr; if (!coerce_files(wk, akw[kw_input].node, akw[kw_input].val, &input_arr)) { return false; } if (!obj_array_flatten_one(wk, input_arr, &input)) { interp_error(wk, akw[kw_input].node, "expected exactly one input"); return false; } } obj_array_push(wk, command, input); obj_array_push(wk, command, make_str(wk, "@OUTPUT@")); obj_array_push(wk, command, replace_string); obj_array_push(wk, command, fallback); obj_array_push(wk, command, make_str(wk, wk->source_root)); if (akw[kw_command].set) { obj_array_extend(wk, command, akw[kw_command].val); } struct make_custom_target_opts opts = { .name = make_str(wk, "vcs_tag"), .input_node = akw[kw_input].node, .output_node = akw[kw_output].node, .input_orig = akw[kw_input].val, .output_orig = akw[kw_output].val, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = command, }; if (!make_custom_target(wk, &opts, res)) { return false; } struct obj_custom_target *tgt = get_obj_custom_target(wk, *res); tgt->flags |= custom_target_build_always_stale; obj_array_push(wk, current_project(wk)->targets, *res); return true; } muon-0.2.0/src/functions/kernel/dependency.c000066400000000000000000000747061441402162300210430ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "buf_size.h" #include "coerce.h" #include "error.h" #include "external/libpkgconf.h" #include "functions/common.h" #include "functions/kernel/dependency.h" #include "functions/kernel/subproject.h" #include "functions/machine.h" #include "functions/string.h" #include "functions/subproject.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" #include "platform/run_cmd.h" enum dependency_lookup_method { // Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. dependency_lookup_method_auto, dependency_lookup_method_pkgconfig, dependency_lookup_method_cmake, // The dependency is provided by the standard library and does not need to be linked dependency_lookup_method_builtin, // Just specify the standard link arguments, assuming the operating system provides the library. dependency_lookup_method_system, // This is only supported on OSX - search the frameworks directory by name. dependency_lookup_method_extraframework, // Detect using the sysconfig module. dependency_lookup_method_sysconfig, // Specify using a "program"-config style tool dependency_lookup_method_config_tool, // Misc dependency_lookup_method_dub, }; enum dep_lib_mode { dep_lib_mode_default, dep_lib_mode_static, dep_lib_mode_shared, }; struct dep_lookup_ctx { obj *res; struct args_kw *default_options, *versions; enum requirement_type requirement; uint32_t err_node; uint32_t fallback_node; obj name; obj names; obj fallback; obj not_found_message; obj modules; enum dep_lib_mode lib_mode; bool disabler; bool fallback_allowed; bool fallback_only; bool from_cache; bool found; }; static enum iteration_result check_dependency_override_iter(struct workspace *wk, void *_ctx, obj n) { struct dep_lookup_ctx *ctx = _ctx; if (ctx->lib_mode != dep_lib_mode_shared) { if (obj_dict_index(wk, wk->dep_overrides_static, n, ctx->res)) { ctx->lib_mode = dep_lib_mode_static; ctx->found = true; return ir_done; } } if (ctx->lib_mode != dep_lib_mode_static) { if (obj_dict_index(wk, wk->dep_overrides_dynamic, n, ctx->res)) { ctx->lib_mode = dep_lib_mode_shared; ctx->found = true; return ir_done; } } return ir_cont; } static bool check_dependency_override(struct workspace *wk, struct dep_lookup_ctx *ctx) { obj_array_foreach(wk, ctx->names, ctx, check_dependency_override_iter); return ctx->found; } static bool check_dependency_cache(struct workspace *wk, struct dep_lookup_ctx *ctx, obj *res) { if (ctx->lib_mode != dep_lib_mode_shared) { if (obj_dict_index(wk, current_project(wk)->dep_cache.static_deps, ctx->name, res)) { ctx->lib_mode = dep_lib_mode_static; return true; } } if (ctx->lib_mode != dep_lib_mode_static) { if (obj_dict_index(wk, current_project(wk)->dep_cache.shared_deps, ctx->name, res)) { ctx->lib_mode = dep_lib_mode_shared; return true; } } return false; } static bool check_dependency_version(struct workspace *wk, obj dep_ver_str, uint32_t err_node, obj ver, bool *res) { if (!ver) { *res = true; return true; } if (!version_compare(wk, err_node, get_str(wk, dep_ver_str), ver, res)) { return false; } return true; } static bool handle_dependency_fallback(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { if (get_option_wrap_mode(wk) == wrap_mode_nofallback) { return true; } obj subproj_name, subproj_dep = 0, subproj; switch (get_obj_array(wk, ctx->fallback)->len) { case 2: obj_array_index(wk, ctx->fallback, 1, &subproj_dep); /* FALLTHROUGH */ case 1: obj_array_index(wk, ctx->fallback, 0, &subproj_name); break; default: interp_error(wk, ctx->err_node, "expected array of length 1-2 for fallback"); return false; } if (ctx->lib_mode != dep_lib_mode_default) { obj libopt; if (ctx->lib_mode == dep_lib_mode_static) { libopt = make_str(wk, "default_library=static"); } else { libopt = make_str(wk, "default_library=shared"); } if (ctx->default_options->set) { if (!obj_array_in(wk, ctx->default_options->val, libopt)) { obj newopts; obj_array_dup(wk, ctx->default_options->val, &newopts); obj_array_push(wk, newopts, libopt); ctx->default_options->val = newopts; } } else { make_obj(wk, &ctx->default_options->val, obj_array); obj_array_push(wk, ctx->default_options->val, libopt); ctx->default_options->set = true; } } if (!subproject(wk, subproj_name, ctx->requirement, ctx->default_options, ctx->versions, &subproj)) { goto not_found; } if (!get_obj_subproject(wk, subproj)->found) { goto not_found; } if (subproj_dep) { if (!subproject_get_variable(wk, ctx->fallback_node, subproj_dep, 0, subproj, ctx->res)) { interp_warning(wk, ctx->fallback_node, "subproject dependency variable %o is not defined", subproj_dep); goto not_found; } } else { if (!check_dependency_override(wk, ctx)) { interp_warning(wk, ctx->fallback_node, "subproject does not override dependency %o", ctx->name); goto not_found; } } if (get_obj_type(wk, *ctx->res) != obj_dependency) { interp_warning(wk, ctx->fallback_node, "overridden dependency is not a dependency object"); goto not_found; } *found = true; return true; not_found: obj_fprintf(wk, log_file(), "fallback %o failed for %o\n", ctx->fallback, ctx->name); *ctx->res = 0; *found = false; return true; } static bool get_dependency_pkgconfig(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *found) { struct pkgconf_info info = { 0 }; *found = false; if (!muon_pkgconf_lookup(wk, ctx->name, ctx->lib_mode == dep_lib_mode_static, &info)) { return true; } obj ver_str = make_str(wk, info.version); bool ver_match; if (!check_dependency_version(wk, ver_str, ctx->err_node, ctx->versions->val, &ver_match)) { return false; } else if (!ver_match) { obj_fprintf(wk, log_file(), "pkgconf found dependency %o, but the version %o does not match the requested version %o\n", ctx->name, ver_str, ctx->versions->val); return true; } make_obj(wk, ctx->res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = ctx->name; dep->version = ver_str; dep->flags |= dep_flag_found; dep->type = dependency_type_pkgconf; dep->dep.link_with = info.libs; dep->dep.link_with_not_found = info.not_found_libs; dep->dep.include_directories = info.includes; dep->dep.compile_args = info.compile_args; dep->dep.link_args = info.link_args; *found = true; return true; } static bool get_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx) { { obj cached_dep; if (check_dependency_cache(wk, ctx, &cached_dep)) { bool ver_match; struct obj_dependency *dep = get_obj_dependency(wk, cached_dep); if (!check_dependency_version(wk, dep->version, ctx->versions->node, ctx->versions->val, &ver_match)) { return false; } if (!ver_match) { return true; } *ctx->res = cached_dep; ctx->found = true; ctx->from_cache = true; return true; } } if (check_dependency_override(wk, ctx)) { return true; } bool force_fallback = false; enum wrap_mode wrap_mode = get_option_wrap_mode(wk); if (!ctx->fallback) { obj provided_fallback; if (obj_dict_index(wk, current_project(wk)->wrap_provides_deps, ctx->name, &provided_fallback)) { ctx->fallback = provided_fallback; } } // implicitly fallback on a subproject named the same as this dependency if (!ctx->fallback && ctx->fallback_allowed) { make_obj(wk, &ctx->fallback, obj_array); obj_array_push(wk, ctx->fallback, ctx->name); } if (ctx->fallback) { obj force_fallback_for, subproj_name; get_option_value(wk, current_project(wk), "force_fallback_for", &force_fallback_for); obj_array_index(wk, ctx->fallback, 0, &subproj_name); force_fallback = wrap_mode == wrap_mode_forcefallback || obj_array_in(wk, force_fallback_for, ctx->name) || obj_dict_in(wk, wk->subprojects, subproj_name); } if (!ctx->found) { if (ctx->fallback && (force_fallback || ctx->fallback_only)) { if (!handle_dependency_fallback(wk, ctx, &ctx->found)) { return false; } } else { if (!get_dependency_pkgconfig(wk, ctx, &ctx->found)) { return false; } if (!ctx->found && ctx->fallback) { if (!handle_dependency_fallback(wk, ctx, &ctx->found)) { return false; } } } } return true; } static enum iteration_result handle_appleframeworks_modules_iter(struct workspace *wk, void *_ctx, obj val) { struct dep_lookup_ctx *ctx = _ctx; struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); obj_array_push(wk, dep->dep.link_args, make_str(wk, "-framework")); obj_array_push(wk, dep->dep.link_args, val); return ir_cont; } static bool handle_special_dependency(struct workspace *wk, struct dep_lookup_ctx *ctx, bool *handled) { if (strcmp(get_cstr(wk, ctx->name), "threads") == 0) { LOG_I("dependency threads found"); *handled = true; make_obj(wk, ctx->res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = ctx->name; dep->flags |= dep_flag_found; dep->type = dependency_type_threads; make_obj(wk, &dep->dep.compile_args, obj_array); obj_array_push(wk, dep->dep.compile_args, make_str(wk, "-pthread")); make_obj(wk, &dep->dep.link_args, obj_array); obj_array_push(wk, dep->dep.link_args, make_str(wk, "-pthread")); } else if (strcmp(get_cstr(wk, ctx->name), "curses") == 0) { *handled = true; ctx->name = make_str(wk, "ncurses"); if (!get_dependency(wk, ctx)) { return false; } if (!ctx->found) { *handled = false; } } else if (strcmp(get_cstr(wk, ctx->name), "appleframeworks") == 0) { *handled = true; if (!ctx->modules) { interp_error(wk, ctx->err_node, "'appleframeworks' dependency requires the modules keyword"); return false; } make_obj(wk, ctx->res, obj_dependency); if (machine_system() == machine_system_darwin) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx->res); dep->name = make_str(wk, "appleframeworks"); dep->flags |= dep_flag_found; dep->type = dependency_type_appleframeworks; make_obj(wk, &dep->dep.link_args, obj_array); obj_array_foreach(wk, ctx->modules, ctx, handle_appleframeworks_modules_iter); } } else if (strcmp(get_cstr(wk, ctx->name), "") == 0) { *handled = true; if (ctx->requirement == requirement_required) { interp_error(wk, ctx->err_node, "dependency '' cannot be required"); return false; } make_obj(wk, ctx->res, obj_dependency); } else { *handled = false; } return true; } static enum iteration_result dependency_iter(struct workspace *wk, void *_ctx, obj name) { bool handled; struct dep_lookup_ctx *parent_ctx = _ctx; struct dep_lookup_ctx ctx = *parent_ctx; ctx.name = name; if (!handle_special_dependency(wk, &ctx, &handled)) { return ir_err; } else if (handled) { ctx.found = true; } else { if (!get_dependency(wk, &ctx)) { return ir_err; } } if (ctx.found) { parent_ctx->name = name; parent_ctx->lib_mode = ctx.lib_mode; parent_ctx->from_cache = ctx.from_cache; parent_ctx->found = true; return ir_done; } else { return ir_cont; } } static enum iteration_result set_dependency_cache_iter(struct workspace *wk, void *_ctx, obj name) { struct dep_lookup_ctx *ctx = _ctx; if (ctx->lib_mode != dep_lib_mode_shared) { obj_dict_set(wk, current_project(wk)->dep_cache.static_deps, name, *ctx->res); } if (ctx->lib_mode != dep_lib_mode_static) { obj_dict_set(wk, current_project(wk)->dep_cache.shared_deps, name, *ctx->res); } return ir_cont; } bool func_dependency(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_required, kw_native, // ignored kw_version, kw_static, kw_modules, // ignored kw_optional_modules, // ignored kw_components, // ignored kw_fallback, kw_allow_fallback, kw_default_options, kw_not_found_message, kw_disabler, kw_method, kw_include_type, }; struct args_kw akw[] = { [kw_required] = { "required", tc_required_kw }, [kw_native] = { "native", obj_bool }, [kw_version] = { "version", ARG_TYPE_ARRAY_OF | obj_string }, [kw_static] = { "static", obj_bool }, [kw_modules] = { "modules", ARG_TYPE_ARRAY_OF | obj_string }, [kw_optional_modules] = { "optional_modules", ARG_TYPE_ARRAY_OF | obj_string }, [kw_components] = { "components", ARG_TYPE_ARRAY_OF | obj_string }, [kw_fallback] = { "fallback", ARG_TYPE_ARRAY_OF | obj_string }, [kw_allow_fallback] = { "allow_fallback", obj_bool }, [kw_default_options] = { "default_options", ARG_TYPE_ARRAY_OF | obj_string }, [kw_not_found_message] = { "not_found_message", obj_string }, [kw_disabler] = { "disabler", obj_bool }, [kw_method] = { "method", obj_string }, [kw_include_type] = { "include_type", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!get_obj_array(wk, an[0].val)->len) { interp_error(wk, an[0].node, "no dependency names specified"); return false; } enum include_type inc_type = include_type_preserve; if (akw[kw_include_type].set) { if (!coerce_include_type(wk, get_str(wk, akw[kw_include_type].val), akw[kw_include_type].node, &inc_type)) { return false; } } enum dependency_lookup_method lookup_method = dependency_lookup_method_auto; if (akw[kw_method].set) { struct { const char *name; enum dependency_lookup_method method; } lookup_method_names[] = { { "auto", dependency_lookup_method_auto }, { "builtin", dependency_lookup_method_builtin }, { "cmake", dependency_lookup_method_cmake }, { "config-tool", dependency_lookup_method_config_tool }, { "dub", dependency_lookup_method_dub }, { "extraframework", dependency_lookup_method_extraframework }, { "pkg-config", dependency_lookup_method_pkgconfig }, { "sysconfig", dependency_lookup_method_sysconfig }, { "system", dependency_lookup_method_system }, // For backwards compatibility { "sdlconfig", dependency_lookup_method_config_tool }, { "cups-config", dependency_lookup_method_config_tool }, { "pcap-config", dependency_lookup_method_config_tool }, { "libwmf-config", dependency_lookup_method_config_tool }, { "qmake", dependency_lookup_method_config_tool }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(lookup_method_names); ++i) { if (str_eql(get_str(wk, akw[kw_method].val), &WKSTR(lookup_method_names[i].name))) { lookup_method = lookup_method_names[i].method; break; } } if (i == ARRAY_LEN(lookup_method_names)) { interp_error(wk, akw[kw_method].node, "invalid dependency method %o", akw[kw_method].val); return false; } if (!(lookup_method == dependency_lookup_method_auto || lookup_method == dependency_lookup_method_pkgconfig || lookup_method == dependency_lookup_method_builtin || lookup_method == dependency_lookup_method_system )) { interp_warning(wk, akw[kw_method].node, "unsupported dependency method %o, falling back to 'auto'", akw[kw_method].val); lookup_method = dependency_lookup_method_auto; } } enum requirement_type requirement; if (!coerce_requirement(wk, &akw[kw_required], &requirement)) { return false; } if (requirement == requirement_skip) { make_obj(wk, res, obj_dependency); struct obj_dependency *dep = get_obj_dependency(wk, *res); obj_array_index(wk, an[0].val, 0, &dep->name); return true; } enum dep_lib_mode lib_mode = dep_lib_mode_default; if (akw[kw_static].set) { if (get_obj_bool(wk, akw[kw_static].val)) { lib_mode = dep_lib_mode_static; } else { lib_mode = dep_lib_mode_shared; } } /* A fallback is allowed if */ bool fallback_allowed = /* - allow_fallback: true */ (akw[kw_allow_fallback].set && get_obj_bool(wk, akw[kw_allow_fallback].val)) /* - allow_fallback is not specified and the requirement is required */ || (!akw[kw_allow_fallback].set && requirement == requirement_required) /* - allow_fallback is not specified and the fallback keyword is * specified with at least one value (i.e. not an empty array) */ || (!akw[kw_allow_fallback].set && akw[kw_fallback].set && get_obj_array(wk, akw[kw_fallback].val)->len); uint32_t fallback_err_node = 0; obj fallback = 0; if (fallback_allowed) { if (akw[kw_fallback].set) { fallback_err_node = akw[kw_fallback].node; fallback = akw[kw_fallback].val; } else if (akw[kw_allow_fallback].set) { fallback_err_node = akw[kw_allow_fallback].node; } else { fallback_err_node = an[0].node; } } struct dep_lookup_ctx ctx = { .res = res, .names = an[0].val, .requirement = requirement, .versions = &akw[kw_version], .err_node = an[0].node, .fallback_node = fallback_err_node, .fallback = fallback, .default_options = &akw[kw_default_options], .not_found_message = akw[kw_not_found_message].val, .lib_mode = lib_mode, .disabler = akw[kw_disabler].set && get_obj_bool(wk, akw[kw_disabler].val), .modules = akw[kw_modules].val, }; if (!obj_array_foreach(wk, an[0].val, &ctx, dependency_iter)) { return false; } if (!ctx.found && fallback_allowed) { ctx.fallback_allowed = fallback_allowed; ctx.fallback_only = true; if (!obj_array_foreach(wk, an[0].val, &ctx, dependency_iter)) { return false; } } if (!ctx.found) { if (ctx.requirement == requirement_required) { LLOG_E("required "); } else { LLOG_W("%s", ""); } obj_fprintf(wk, log_file(), "dependency %o not found", an[0].val); if (ctx.not_found_message) { obj_fprintf(wk, log_file(), ", %#o", ctx.not_found_message); } log_plain("\n"); if (ctx.requirement == requirement_required) { interp_error(wk, ctx.err_node, "required dependency not found"); return false; } else { if (ctx.disabler) { *ctx.res = disabler_id; } else { make_obj(wk, ctx.res, obj_dependency); get_obj_dependency(wk, *ctx.res)->name = ctx.name; } } } else if (!str_eql(get_str(wk, ctx.name), &WKSTR(""))) { struct obj_dependency *dep = get_obj_dependency(wk, *ctx.res); LLOG_I("found dependency "); if (dep->type == dependency_type_declared) { obj_fprintf(wk, log_file(), "%o (declared dependency)", ctx.name); } else { log_plain("%s", get_cstr(wk, dep->name)); } if (dep->version) { log_plain(" version %s", get_cstr(wk, dep->version)); } if (ctx.lib_mode == dep_lib_mode_static) { log_plain(" static"); } log_plain("\n"); if (dep->type == dependency_type_declared) { L("(%s)", get_cstr(wk, dep->name)); } } if (get_obj_type(wk, *res) == obj_dependency) { struct obj_dependency *dep = get_obj_dependency(wk, *res); if (ctx.from_cache) { obj dup; make_obj(wk, &dup, obj_dependency); struct obj_dependency *newdep = get_obj_dependency(wk, dup); *newdep = *dep; dep = newdep; *res = dup; } // set the include type if the return value is not a disabler dep->include_type = inc_type; if (dep->flags & dep_flag_found && !ctx.from_cache) { obj_array_foreach(wk, ctx.names, &ctx, set_dependency_cache_iter); } } return true; } struct process_dependency_sources_ctx { uint32_t err_node; obj res; }; static enum iteration_result coerce_dependency_sources_iter(struct workspace *wk, void *_ctx, obj val) { struct process_dependency_sources_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_generated_list: obj_array_push(wk, ctx->res, val); break; default: { obj res; if (!coerce_files(wk, ctx->err_node, val, &res)) { return ir_err; } obj_array_extend_nodup(wk, ctx->res, res); } } return ir_cont; } bool func_declare_dependency(struct workspace *wk, obj _, uint32_t args_node, obj *res) { enum kwargs { kw_sources, kw_link_with, kw_link_whole, kw_link_args, kw_dependencies, kw_version, kw_include_directories, kw_variables, kw_compile_args, }; struct args_kw akw[] = { [kw_sources] = { "sources", ARG_TYPE_ARRAY_OF | tc_coercible_files | tc_generated_list }, [kw_link_with] = { "link_with", tc_link_with_kw }, [kw_link_whole] = { "link_whole", tc_link_with_kw }, [kw_link_args] = { "link_args", ARG_TYPE_ARRAY_OF | obj_string }, [kw_dependencies] = { "dependencies", ARG_TYPE_ARRAY_OF | tc_dependency }, [kw_version] = { "version", obj_string }, [kw_include_directories] = { "include_directories", ARG_TYPE_ARRAY_OF | tc_coercible_inc }, [kw_variables] = { "variables", tc_array | tc_dict }, [kw_compile_args] = { "compile_args", ARG_TYPE_ARRAY_OF | obj_string }, 0 }; if (!interp_args(wk, args_node, NULL, NULL, akw)) { return false; } if (akw[kw_include_directories].set) { obj inc_dirs; if (!coerce_include_dirs(wk, akw[kw_include_directories].node, akw[kw_include_directories].val, false, &inc_dirs)) { return false; } akw[kw_include_directories].val = inc_dirs; } struct obj_dependency *dep; make_obj(wk, res, obj_dependency); dep = get_obj_dependency(wk, *res); build_dep_init(wk, &dep->dep); dep->name = make_strf(wk, "%s:declared_dep@%s:%d", get_cstr(wk, current_project(wk)->cfg.name), wk->src->label, get_node(wk->ast, args_node)->line); dep->flags |= dep_flag_found; dep->type = dependency_type_declared; if (akw[kw_variables].set && !coerce_key_value_dict(wk, akw[kw_variables].node, akw[kw_variables].val, &dep->variables)) { return false; } if (akw[kw_link_args].set) { obj_array_extend_nodup(wk, dep->dep.link_args, akw[kw_link_args].val); } if (akw[kw_compile_args].set) { obj_array_extend_nodup(wk, dep->dep.compile_args, akw[kw_compile_args].val); } if (akw[kw_version].set) { dep->version = akw[kw_version].val; } else { dep->version = current_project(wk)->cfg.version; } if (akw[kw_sources].set) { struct process_dependency_sources_ctx ctx = { .err_node = akw[kw_sources].node, .res = dep->dep.sources, }; if (!obj_array_foreach_flat(wk, akw[kw_sources].val, &ctx, coerce_dependency_sources_iter)) { return false; } } if (akw[kw_link_with].set) { if (!dep_process_link_with(wk, akw[kw_link_with].node, akw[kw_link_with].val, &dep->dep)) { return false; } } if (akw[kw_link_whole].set) { if (!dep_process_link_whole(wk, akw[kw_link_whole].node, akw[kw_link_whole].val, &dep->dep)) { return false; } } if (akw[kw_include_directories].set) { dep_process_includes(wk, akw[kw_include_directories].val, include_type_preserve, dep->dep.include_directories); } if (akw[kw_dependencies].set) { dep_process_deps(wk, akw[kw_dependencies].val, &dep->dep); } return true; } /* */ static bool skip_if_present(struct workspace *wk, obj arr, obj val) { if (hash_get(&wk->obj_hash, &val)) { return true; } hash_set(&wk->obj_hash, &val, true); return false; } struct dep_process_includes_ctx { obj dest; enum include_type include_type; }; static enum iteration_result dep_process_includes_iter(struct workspace *wk, void *_ctx, obj inc_id) { struct dep_process_includes_ctx *ctx = _ctx; struct obj_include_directory *inc = get_obj_include_directory(wk, inc_id); bool new_is_system = inc->is_system; switch (ctx->include_type) { case include_type_preserve: break; case include_type_system: new_is_system = true; break; case include_type_non_system: new_is_system = false; break; } if (inc->is_system != new_is_system) { make_obj(wk, &inc_id, obj_include_directory); struct obj_include_directory *new_inc = get_obj_include_directory(wk, inc_id); *new_inc = *inc; new_inc->is_system = new_is_system; } obj_array_push(wk, ctx->dest, inc_id); return ir_cont; } void dep_process_includes(struct workspace *wk, obj arr, enum include_type include_type, obj dest) { obj_array_foreach_flat(wk, arr, &(struct dep_process_includes_ctx) { .include_type = include_type, .dest = dest, }, dep_process_includes_iter); } void build_dep_init(struct workspace *wk, struct build_dep *dep) { if (!dep->include_directories) { make_obj(wk, &dep->include_directories, obj_array); } if (!dep->link_with) { make_obj(wk, &dep->link_with, obj_array); } if (!dep->link_whole) { make_obj(wk, &dep->link_whole, obj_array); } if (!dep->link_with_not_found) { make_obj(wk, &dep->link_with_not_found, obj_array); } if (!dep->link_args) { make_obj(wk, &dep->link_args, obj_array); } if (!dep->compile_args) { make_obj(wk, &dep->compile_args, obj_array); } if (!dep->order_deps) { make_obj(wk, &dep->order_deps, obj_array); } if (!dep->rpath) { make_obj(wk, &dep->rpath, obj_array); } if (!dep->sources) { make_obj(wk, &dep->sources, obj_array); } } static void merge_build_deps(struct workspace *wk, struct build_dep *src, struct build_dep *dest, bool dep) { build_dep_init(wk, dest); dest->link_language = coalesce_link_languages(src->link_language, dest->link_language); if (src->link_with) { obj_array_extend(wk, dest->link_with, src->link_with); } if (src->link_with_not_found) { obj_array_extend(wk, dest->link_with_not_found, src->link_with_not_found); } if (src->link_whole) { obj_array_extend(wk, dest->link_whole, src->link_whole); } if (dep && src->include_directories) { obj_array_extend(wk, dest->include_directories, src->include_directories); } if (src->link_args) { obj_array_extend(wk, dest->link_args, src->link_args); } if (dep && src->compile_args) { obj_array_extend(wk, dest->compile_args, src->compile_args); } if (src->rpath) { obj_array_extend(wk, dest->rpath, src->rpath); } if (src->order_deps) { obj_array_extend(wk, dest->order_deps, src->order_deps); } if (dep && src->sources) { obj_array_extend(wk, dest->sources, src->sources); } } static void obj_array_dedup_in_place(struct workspace *wk, obj *arr) { if (!*arr) { return; } obj dedupd; obj_array_dedup(wk, *arr, &dedupd); *arr = dedupd; } static enum iteration_result dedup_link_args_iter(struct workspace *wk, void *_ctx, obj val) { obj new_args = *(obj *)_ctx; static const char *known[] = { "-pthread", }; const char *s = get_cstr(wk, val); uint32_t i; for (i = 0; i < ARRAY_LEN(known); ++i) { if (strcmp(known[i], s) == 0) { if (obj_array_in(wk, new_args, val)) { return ir_cont; } else { break; } } } obj_array_push(wk, new_args, val); return ir_cont; } static void dedup_build_dep(struct workspace *wk, struct build_dep *dep) { obj_array_dedup_in_place(wk, &dep->link_with); obj_array_dedup_in_place(wk, &dep->link_with_not_found); obj_array_dedup_in_place(wk, &dep->link_whole); obj_array_dedup_in_place(wk, &dep->raw.deps); obj_array_dedup_in_place(wk, &dep->raw.link_with); obj_array_dedup_in_place(wk, &dep->raw.link_whole); obj_array_dedup_in_place(wk, &dep->include_directories); obj_array_dedup_in_place(wk, &dep->rpath); obj_array_dedup_in_place(wk, &dep->order_deps); obj_array_dedup_in_place(wk, &dep->sources); obj new_link_args; make_obj(wk, &new_link_args, obj_array); obj_array_foreach(wk, dep->link_args, &new_link_args, dedup_link_args_iter); dep->link_args = new_link_args; obj new_compile_args; make_obj(wk, &new_compile_args, obj_array); obj_array_foreach(wk, dep->compile_args, &new_compile_args, dedup_link_args_iter); dep->compile_args = new_compile_args; } struct dep_process_link_with_ctx { struct build_dep *dest; bool link_whole; uint32_t err_node; }; static enum iteration_result dep_process_link_with_iter(struct workspace *wk, void *_ctx, obj val) { struct dep_process_link_with_ctx *ctx = _ctx; if (skip_if_present(wk, ctx->dest->raw.link_with, val)) { return ir_cont; } enum obj_type t = get_obj_type(wk, val); /* obj_fprintf(wk, log_file(), "link_with: %o\n", val); */ obj dest_link_with; if (ctx->link_whole) { dest_link_with = ctx->dest->link_whole; } else { dest_link_with = ctx->dest->link_with; } switch (t) { case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); const char *path = get_cstr(wk, tgt->build_path); if (ctx->link_whole && tgt->type != tgt_static_library) { interp_error(wk, ctx->err_node, "link whole only accepts static libraries"); return ir_err; } if (tgt->type != tgt_executable) { obj_array_push(wk, dest_link_with, make_str(wk, path)); } // calculate rpath for this target // we always want an absolute path here, regardles of // ctx->relativize if (tgt->type != tgt_static_library) { SBUF(abs); SBUF(dir); const char *p; path_dirname(wk, &dir, path); if (path_is_absolute(dir.buf)) { p = dir.buf; } else { path_join(wk, &abs, wk->build_root, dir.buf); p = abs.buf; } obj s = make_str(wk, p); if (!obj_array_in(wk, ctx->dest->rpath, s)) { obj_array_push(wk, ctx->dest->rpath, s); } } merge_build_deps(wk, &tgt->dep, ctx->dest, false); break; } case obj_custom_target: { obj_array_foreach(wk, get_obj_custom_target(wk, val)->output, ctx, dep_process_link_with_iter); break; } case obj_file: { obj_array_push(wk, dest_link_with, *get_obj_file(wk, val)); break; } case obj_string: obj_array_push(wk, dest_link_with, val); break; default: interp_error(wk, ctx->err_node, "invalid type for link_with: '%s'", obj_type_to_s(t)); return ir_err; } return ir_cont; } bool dep_process_link_with(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.link_with = arr; hash_clear(&wk->obj_hash); if (!obj_array_foreach_flat(wk, arr, &(struct dep_process_link_with_ctx) { .dest = dest, .err_node = err_node, }, dep_process_link_with_iter)) { return false; } dedup_build_dep(wk, dest); return true; } bool dep_process_link_whole(struct workspace *wk, uint32_t err_node, obj arr, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.link_whole = arr; hash_clear(&wk->obj_hash); if (!obj_array_foreach_flat(wk, arr, &(struct dep_process_link_with_ctx) { .dest = dest, .link_whole = true, .err_node = err_node, }, dep_process_link_with_iter)) { return false; } dedup_build_dep(wk, dest); return true; } static enum iteration_result dep_process_deps_iter(struct workspace *wk, void *_ctx, obj val) { struct build_dep *dest = _ctx; /* obj_fprintf(wk, log_file(), "dep: %o\n", val); */ if (skip_if_present(wk, dest->raw.deps, val)) { return ir_cont; } struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found)) { return ir_cont; } merge_build_deps(wk, &dep->dep, dest, true); return ir_cont; } void dep_process_deps(struct workspace *wk, obj deps, struct build_dep *dest) { build_dep_init(wk, dest); dest->raw.deps = deps; hash_clear(&wk->obj_hash); obj_array_foreach(wk, deps, dest, dep_process_deps_iter); dedup_build_dep(wk, dest); } muon-0.2.0/src/functions/kernel/install.c000066400000000000000000000245701441402162300203650ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "coerce.h" #include "error.h" #include "functions/kernel/install.h" #include "install.h" #include "lang/interpreter.h" #include "options.h" #include "platform/path.h" bool func_install_subdir(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_install_tag, kw_exclude_directories, kw_exclude_files, kw_strip_directory, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string, .required = true }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_exclude_directories] = { "exclude_directories", ARG_TYPE_ARRAY_OF | obj_string }, [kw_exclude_files] = { "exclude_files", ARG_TYPE_ARRAY_OF | obj_string }, [kw_strip_directory] = { "strip_directory", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } bool strip_directory = akw[kw_strip_directory].set ? get_obj_bool(wk, akw[kw_strip_directory].val) : false; obj dest = akw[kw_install_dir].val; if (!strip_directory) { SBUF(path); SBUF(name); char *sep; const char *name_tail; sbuf_pushs(wk, &name, get_cstr(wk, an[0].val)); name_tail = name.buf; // strip the first part of the name if ((sep = strchr(name.buf, PATH_SEP))) { *sep = 0; name_tail = sep + 1; } path_join(wk, &path, get_cstr(wk, dest), name_tail); dest = sbuf_into_str(wk, &path); } SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->cwd), get_cstr(wk, an[0].val)); obj src = sbuf_into_str(wk, &path); struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, src, dest, akw[kw_install_mode].val))) { return false; } tgt->exclude_directories = akw[kw_exclude_directories].val; tgt->exclude_files = akw[kw_exclude_files].val; tgt->type = install_target_subdir; return true; } struct install_man_ctx { obj mode; obj install_dir; obj locale; uint32_t err_node; bool default_install_dir; }; static enum iteration_result install_man_iter(struct workspace *wk, void *_ctx, obj val) { struct install_man_ctx *ctx = _ctx; obj src = *get_obj_file(wk, val); SBUF(man); path_basename(wk, &man, get_cstr(wk, src)); size_t len = man.len; assert(len > 0); --len; if (len <= 1 || man.buf[len - 1] != '.' || man.buf[len] < '0' || man.buf[len] > '9') { interp_error(wk, ctx->err_node, "invalid path to man page"); return ir_err; } obj install_dir; if (ctx->default_install_dir) { install_dir = make_strf(wk, "%s/man%c", get_cstr(wk, ctx->install_dir), man.buf[len]); } else { install_dir = ctx->install_dir; } const char *basename = man.buf; if (ctx->locale) { char *dot = strchr(man.buf, '.'); assert(dot); if (str_startswith(&WKSTR(dot + 1), get_str(wk, ctx->locale))) { *dot = '\0'; obj new_man = make_strf(wk, "%s.%c", man.buf, man.buf[len]); basename = get_cstr(wk, new_man); } } SBUF(path); path_join(wk, &path, get_cstr(wk, install_dir), basename); obj dest = sbuf_into_str(wk, &path); if (!push_install_target(wk, src, dest, ctx->mode)) { return ir_err; } return ir_cont; } bool func_install_man(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_coercible_files }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_locale, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_locale] = { "locale", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct install_man_ctx ctx = { .err_node = an[0].node, .mode = akw[kw_install_mode].val, .install_dir = akw[kw_install_dir].val, .default_install_dir = false, }; if (!akw[kw_install_dir].set) { obj mandir; get_option_value(wk, current_project(wk), "mandir", &mandir); if (akw[kw_locale].set) { SBUF(path); path_join(wk, &path, get_cstr(wk, mandir), get_cstr(wk, akw[kw_locale].val)); ctx.install_dir = sbuf_into_str(wk, &path); ctx.locale = akw[kw_locale].val; } else { ctx.install_dir = mandir; } ctx.default_install_dir = true; } obj manpages; if (!coerce_files(wk, an[0].node, an[0].val, &manpages)) { return false; } return obj_array_foreach(wk, manpages, &ctx, install_man_iter); } bool func_install_symlink(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_tag, kw_pointing_to, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string, .required = true }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_pointing_to] = { "pointing_to", obj_string, .required = true }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } SBUF(path); path_join(wk, &path, get_cstr(wk, akw[kw_install_dir].val), get_cstr(wk, an[0].val)); struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, akw[kw_pointing_to].val, sbuf_into_str(wk, &path), 0))) { return false; } tgt->type = install_target_symlink; return true; } struct install_emptydir_ctx { obj mode; }; static enum iteration_result install_emptydir_iter(struct workspace *wk, void *_ctx, obj val) { struct install_emptydir_ctx *ctx = _ctx; struct obj_install_target *tgt; if (!(tgt = push_install_target(wk, make_str(wk, ""), val, ctx->mode))) { return ir_err; } tgt->type = install_target_emptydir; return ir_cont; } bool func_install_emptydir(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_mode, kw_install_tag, }; struct args_kw akw[] = { [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct install_emptydir_ctx ctx = { .mode = akw[kw_install_mode].val, }; return obj_array_foreach(wk, an[0].val, &ctx, install_emptydir_iter); } struct install_data_rename_ctx { obj rename; obj mode; obj dest; uint32_t i; uint32_t node; }; static enum iteration_result install_data_rename_iter(struct workspace *wk, void *_ctx, obj val) { struct install_data_rename_ctx *ctx = _ctx; obj src = *get_obj_file(wk, val); obj dest; obj rename; obj_array_index(wk, ctx->rename, ctx->i, &rename); SBUF(d); path_join(wk, &d, get_cstr(wk, ctx->dest), get_cstr(wk, rename)); dest = sbuf_into_str(wk, &d); push_install_target(wk, src, dest, ctx->mode); ++ctx->i; return ir_cont; } bool func_install_data(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_file | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_install_tag, kw_rename, kw_sources, kw_preserve_path, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_install_tag] = { "install_tag", obj_string }, // TODO [kw_rename] = { "rename", ARG_TYPE_ARRAY_OF | obj_string }, [kw_sources] = { "sources", ARG_TYPE_ARRAY_OF | tc_file | tc_string }, [kw_preserve_path] = { "preserve_path", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (akw[kw_rename].set && akw[kw_preserve_path].set) { interp_error(wk, akw[kw_preserve_path].node, "rename keyword conflicts with preserve_path"); return false; } obj install_dir; if (akw[kw_install_dir].set) { install_dir = akw[kw_install_dir].val; } else { obj install_dir_base; get_option_value(wk, current_project(wk), "datadir", &install_dir_base); SBUF(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, current_project(wk)->cfg.name)); install_dir = sbuf_into_str(wk, &buf); } obj sources = an[0].val; uint32_t err_node = an[0].node; if (akw[kw_sources].set) { obj_array_extend_nodup(wk, sources, akw[kw_sources].val); err_node = akw[kw_sources].node; } if (akw[kw_rename].set) { if (get_obj_array(wk, akw[kw_rename].val)->len != get_obj_array(wk, sources)->len) { interp_error(wk, akw[kw_rename].node, "number of elements in rename != number of sources"); return false; } struct install_data_rename_ctx ctx = { .node = err_node, .mode = akw[kw_install_mode].val, .rename = akw[kw_rename].val, .dest = install_dir, }; obj coerced; if (!coerce_files(wk, err_node, sources, &coerced)) { return false; } return obj_array_foreach(wk, coerced, &ctx, install_data_rename_iter); } else { bool preserve_path = akw[kw_preserve_path].set && get_obj_bool(wk, akw[kw_preserve_path].val); return push_install_targets(wk, err_node, sources, install_dir, akw[kw_install_mode].val, preserve_path); } } bool func_install_headers(struct workspace *wk, obj _, uint32_t args_node, obj *ret) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_file | tc_string }, ARG_TYPE_NULL }; enum kwargs { kw_install_dir, kw_install_mode, kw_subdir, kw_preserve_path, }; struct args_kw akw[] = { [kw_install_dir] = { "install_dir", obj_string }, [kw_install_mode] = { "install_mode", tc_install_mode_kw }, [kw_subdir] = { "subdir", obj_string }, [kw_preserve_path] = { "preserve_path", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (akw[kw_install_dir].set && akw[kw_subdir].set) { interp_error(wk, akw[kw_subdir].node, "subdir may not be set if install_dir is set"); return false; } obj install_dir_base; if (akw[kw_install_dir].set) { install_dir_base = akw[kw_install_dir].val; } else { get_option_value(wk, current_project(wk), "includedir", &install_dir_base); } obj install_dir; if (akw[kw_subdir].set) { SBUF(buf); path_join(wk, &buf, get_cstr(wk, install_dir_base), get_cstr(wk, akw[kw_subdir].val)); install_dir = sbuf_into_str(wk, &buf); } else { install_dir = install_dir_base; } bool preserve_path = akw[kw_preserve_path].set && get_obj_bool(wk, akw[kw_preserve_path].val); return push_install_targets(wk, an[0].node, an[0].val, install_dir, akw[kw_install_mode].val, preserve_path); } muon-0.2.0/src/functions/kernel/options.c000066400000000000000000000147011441402162300204050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "functions/common.h" #include "functions/kernel/options.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" static bool build_option_type_from_s(struct workspace *wk, uint32_t node, uint32_t name, enum build_option_type *res) { static const char *build_option_type_name[] = { [op_string] = "string", [op_boolean] = "boolean", [op_combo] = "combo", [op_integer] = "integer", [op_array] = "array", [op_feature] = "feature", }; enum build_option_type type; for (type = 0; type < build_option_type_count; ++type) { if (strcmp(build_option_type_name[type], get_cstr(wk, name)) == 0) { *res = type; return true; } } interp_error(wk, node, "invalid option type '%s'", get_cstr(wk, name)); return false; } static bool validate_option_name(struct workspace *wk, uint32_t err_node, obj name) { uint32_t i; const struct str *s = get_str(wk, name); for (i = 0; i < s->len; ++i) { if (('a' <= s->s[i] && s->s[i] <= 'z') || ('A' <= s->s[i] && s->s[i] <= 'Z') || ('0' <= s->s[i] && s->s[i] <= '9') || (s->s[i] == '-') || (s->s[i] == '_') ) { continue; } interp_error(wk, err_node, "option name may not contain '%c'", s->s[i]); return false; } return true; } bool func_option(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_type, kw_value, kw_description, kw_choices, kw_max, kw_min, kw_yield, kw_deprecated, kwargs_count, kw_kind = kwargs_count, }; struct args_kw akw[] = { [kw_type] = { "type", obj_string }, [kw_value] = { "value", tc_any }, [kw_description] = { "description", obj_string }, [kw_choices] = { "choices", obj_array }, [kw_max] = { "max", obj_number }, [kw_min] = { "min", obj_number }, [kw_yield] = { "yield", obj_bool }, [kw_deprecated] = { "deprecated", tc_string | tc_bool | tc_array | tc_dict }, [kw_kind] = { 0 }, 0 }; if (initializing_builtin_options) { akw[kw_kind] = (struct args_kw) { "kind", tc_string }; } if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!akw[kw_type].set) { interp_error(wk, args_node, "missing required keyword 'type'"); return false; } enum build_option_type type; if (!build_option_type_from_s(wk, akw[kw_type].node, akw[kw_type].val, &type)) { return false; } enum keyword_req { kw_opt, // optional kw_req, // required kw_inv, // invalid }; static const enum keyword_req keyword_validity[build_option_type_count][kwargs_count] = { /* kw_type, kw_value, kw_description, kw_choices, kw_max, kw_min, kw_yield, kw_deprecated */ [op_string] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_boolean] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_combo] = { kw_req, kw_opt, kw_opt, kw_req, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_integer] = { kw_req, kw_req, kw_opt, kw_inv, kw_opt, kw_opt, kw_opt, kw_opt, }, [op_array] = { kw_req, kw_opt, kw_opt, kw_opt, kw_inv, kw_inv, kw_opt, kw_opt, }, [op_feature] = { kw_req, kw_opt, kw_opt, kw_inv, kw_inv, kw_inv, kw_opt, kw_opt, }, }; uint32_t i; for (i = 0; i < kwargs_count; ++i) { switch (keyword_validity[type][i]) { case kw_opt: break; case kw_inv: if (akw[i].set) { interp_error(wk, akw[i].node, "invalid keyword for option type"); return false; } break; case kw_req: if (!akw[i].set) { interp_error(wk, args_node, "missing keyword '%s' for option type", akw[i].key); return false; } break; default: assert(false && "unreachable"); } } obj val = 0; if (akw[kw_value].set) { val = akw[kw_value].val; } else { switch (type) { case op_string: val = make_str(wk, ""); break; case op_boolean: make_obj(wk, &val, obj_bool); set_obj_bool(wk, val, true); break; case op_combo: if (!get_obj_array(wk, akw[kw_choices].val)->len) { interp_error(wk, akw[kw_choices].node, "combo option with no choices"); return false; } obj_array_index(wk, akw[kw_choices].val, 0, &val); break; case op_array: if (akw[kw_choices].set) { val = akw[kw_choices].val; } else { make_obj(wk, &val, obj_array); } break; case op_feature: make_obj(wk, &val, obj_feature_opt); set_obj_feature_opt(wk, val, feature_opt_auto); break; default: UNREACHABLE_RETURN; } } obj opt; make_obj(wk, &opt, obj_option); struct obj_option *o = get_obj_option(wk, opt); o->name = an[0].val; o->type = type; o->min = akw[kw_min].val; o->max = akw[kw_max].val; o->choices = akw[kw_choices].val; o->yield = akw[kw_yield].set && get_obj_bool(wk, akw[kw_yield].val); o->description = akw[kw_description].val; if (akw[kw_deprecated].set) { switch (get_obj_type(wk, akw[kw_deprecated].val)) { case obj_array: typecheck_array(wk, akw[kw_deprecated].node, akw[kw_deprecated].val, obj_string); break; case obj_dict: typecheck_dict(wk, akw[kw_deprecated].node, akw[kw_deprecated].val, obj_string); break; case obj_string: case obj_bool: break; default: UNREACHABLE; } o->deprecated = akw[kw_deprecated].val; } if (akw[kw_kind].set) { if (str_eql(&WKSTR("default"), get_str(wk, akw[kw_kind].val))) { o->kind = build_option_kind_default; } else if (str_eql(&WKSTR("prefixed_dir"), get_str(wk, akw[kw_kind].val))) { o->kind = build_option_kind_prefixed_dir; } else { interp_error(wk, akw[kw_kind].node, "invalid kind: %o", akw[kw_kind].val); return false; } } obj opts; if (wk->projects.len) { if (!validate_option_name(wk, an[0].node, an[0].val)) { return false; } opts = current_project(wk)->opts; } else { opts = wk->global_opts; } if (!create_option(wk, args_node, opts, opt, val)) { return false; } return true; } bool func_get_option(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj opt; if (!get_option(wk, current_project(wk), get_str(wk, an[0].val), &opt)) { interp_error(wk, an[0].node, "undefined option"); return false; } struct obj_option *o = get_obj_option(wk, opt); *res = o->val; return true; } muon-0.2.0/src/functions/kernel/subproject.c000066400000000000000000000113321441402162300210670ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/kernel/subproject.h" #include "functions/string.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" #include "wrap.h" static bool subproject_prepare(struct workspace *wk, struct sbuf *cwd_buf, const char **cwd, struct sbuf *build_dir_buf, const char **build_dir, bool required, bool *found) { if (!fs_dir_exists(*cwd)) { bool wrap_ok = false; SBUF(wrap_path); SBUF(base_path); sbuf_pushf(wk, &wrap_path, "%s.wrap", *cwd); if (!fs_file_exists(wrap_path.buf)) { goto wrap_done; } path_dirname(wk, &base_path, *cwd); struct wrap wrap = { 0 }; enum wrap_mode wrap_mode = get_option_wrap_mode(wk); if (!wrap_handle(wrap_path.buf, base_path.buf, &wrap, wrap_mode != wrap_mode_nodownload)) { goto wrap_cleanup; } if (wrap.fields[wf_directory]) { path_join(wk, cwd_buf, base_path.buf, wrap.fields[wf_directory]); path_dirname(wk, &base_path, *build_dir); path_join(wk, build_dir_buf, base_path.buf, wrap.fields[wf_directory]); *cwd = cwd_buf->buf; *build_dir = build_dir_buf->buf; } wrap_ok = true; wrap_cleanup: wrap_destroy(&wrap); wrap_done: if (!wrap_ok) { if (required) { LOG_E("project %s not found", *cwd); return false; } else { *found = false; return true; } } } SBUF(src); path_join(wk, &src, *cwd, "meson.build"); if (!fs_file_exists(src.buf)) { if (required) { LOG_E("project %s does not contain a meson.build", *cwd); return false; } else { *found = false; return true; } } *found = true; return true; } bool subproject(struct workspace *wk, obj name, enum requirement_type req, struct args_kw *default_options, struct args_kw *versions, obj *res) { // don't re-evaluate the same subproject if (obj_dict_index(wk, wk->subprojects, name, res)) { return true; } make_obj(wk, res, obj_subproject); if (req == requirement_skip) { return true; } const char *subproj_name = get_cstr(wk, name); SBUF(cwd); SBUF(build_dir); path_join(wk, &cwd, get_cstr(wk, current_project(wk)->source_root), get_cstr(wk, current_project(wk)->subprojects_dir)); path_push(wk, &cwd, subproj_name); path_join(wk, &build_dir, get_cstr(wk, current_project(wk)->build_root), get_cstr(wk, current_project(wk)->subprojects_dir)); path_push(wk, &build_dir, subproj_name); uint32_t subproject_id = 0; bool found; const char *sp_cwd = cwd.buf, *sp_build_dir = build_dir.buf; SBUF(sp_cwd_buf); SBUF(sp_build_dir_buf); if (!subproject_prepare(wk, &sp_cwd_buf, &sp_cwd, &sp_build_dir_buf, &sp_build_dir, req == requirement_required, &found)) { return false; } if (!found) { return true; } if (default_options && default_options->set) { if (!parse_and_set_default_options(wk, default_options->node, default_options->val, name, true)) { return false; } } if (!eval_project(wk, subproj_name, sp_cwd, sp_build_dir, &subproject_id)) { goto not_found; } if (versions && versions->set) { struct project *subp = darr_get(&wk->projects, subproject_id); bool compare_result; if (!version_compare(wk, versions->node, get_str(wk, subp->cfg.version), versions->val, &compare_result)) { goto not_found; } if (!compare_result) { if (req == requirement_required) { interp_error(wk, versions->node, "subproject version mismatch; wanted %o, got %o", versions->val, subp->cfg.version); goto not_found; } } } make_obj(wk, res, obj_subproject); struct obj_subproject *sub = get_obj_subproject(wk, *res); sub->id = subproject_id; sub->found = true; obj_dict_set(wk, wk->subprojects, name, *res); if (fs_dir_exists(wk->build_root)) { if (!fs_mkdir_p(build_dir.buf)) { return false; } } return true; not_found: if (subproject_id) { struct project *proj = darr_get(&wk->projects, subproject_id); proj->not_ok = true; } return req != requirement_required; } bool func_subproject(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_default_options, kw_required, kw_version, }; struct args_kw akw[] = { [kw_default_options] = { "default_options", ARG_TYPE_ARRAY_OF | obj_string }, [kw_required] = { "required", tc_required_kw }, [kw_version] = { "version", ARG_TYPE_ARRAY_OF | obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } enum requirement_type req; if (!coerce_requirement(wk, &akw[kw_required], &req)) { return false; } if (!subproject(wk, an[0].val, req, &akw[kw_default_options], &akw[kw_version], res)) { return false; } return true; } muon-0.2.0/src/functions/machine.c000066400000000000000000000155551441402162300170460ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "error.h" #include "functions/common.h" #include "functions/machine.h" #include "lang/interpreter.h" #include "log.h" #include "platform/uname.h" static const char *known_cpu_families[] = { "aarch64", "alpha", "arc", "arm", "avr", "c2000", "csky", "dspic", "e2k", "ft32", "ia64", "loongarch64", "m68k", "microblaze", "mips", "mips64", "msp430", "parisc", "pic24", "ppc", "ppc64", "riscv32", "riscv64", "rl78", "rx", "s390", "s390x", "sh4", "sparc", "sparc64", "wasm32", "wasm64", "x86", "x86_64", }; static const char * machine_system_to_s(enum machine_system sys) { switch (sys) { case machine_system_dragonfly: return "dragonfly"; case machine_system_freebsd: return "freebsd"; case machine_system_gnu: return "gnu"; case machine_system_haiku: return "haiku"; case machine_system_linux: return "linux"; case machine_system_netbsd: return "netbsd"; case machine_system_openbsd: return "openbsd"; case machine_system_sunos: return "sunos"; case machine_system_android: return "android"; case machine_system_emscripten: return "emscripten"; case machine_system_windows: return "windows"; case machine_system_cygwin: return "cygwin"; case machine_system_msys2: return "msys2"; case machine_system_darwin: return "darwin"; case machine_system_unknown: return "unknown"; } UNREACHABLE_RETURN; } enum machine_system machine_system(void) { const char *sysname; if (!uname_sysname(&sysname)) { return machine_system_unknown; } // The Cygwin environment for Windows if (str_startswith(&WKSTR(sysname), &WKSTR("cygwin_nt"))) { return machine_system_cygwin; } // The MSYS2 environment for Windows if (str_startswith(&WKSTR(sysname), &WKSTR("msys_nt"))) { return machine_system_msys2; } const struct { const char *name; enum machine_system sys; } map[] = { { "darwin", machine_system_darwin }, // Either OSX or iOS { "dragonfly", machine_system_dragonfly }, // DragonFly BSD { "freebsd", machine_system_freebsd }, // FreeBSD and its derivatives { "gnu", machine_system_gnu }, // GNU Hurd { "haiku", machine_system_haiku }, { "linux", machine_system_linux }, { "netbsd", machine_system_netbsd }, { "openbsd", machine_system_openbsd }, { "sunos", machine_system_sunos }, // illumos and Solaris // TODO: These probably need more than just a simple mapping { "android", machine_system_android }, // By convention only, subject to change { "emscripten", machine_system_emscripten }, // Emscripten's Javascript environment { "windows", machine_system_windows }, // Any version of Windows }; uint32_t i; for (i = 0; i < ARRAY_LEN(map); ++i) { if (strcmp(map[i].name, sysname) == 0) { return map[i].sys; } } return machine_system_unknown; } static bool func_machine_system(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, machine_system_to_s(machine_system())); return true; } static bool func_machine_endian(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } enum endianness e; if (!uname_endian(&e)) { return false; } const char *s = NULL; switch (e) { case little_endian: s = "little"; break; case big_endian: s = "big"; break; } *res = make_str(wk, s); return true; } static void machine_cpu_normalize_base(const char **machine_cstr, const char **norm) { const struct str *machine; *norm = NULL; if (!uname_machine(machine_cstr)) { LOG_E("unable to determine cpu information"); *machine_cstr = "unknown"; return; } machine = &WKSTR(*machine_cstr); if (str_startswith(machine, &WKSTR("aarch64"))) { *norm = "aarch64"; } else if (str_startswith(machine, &WKSTR("earm"))) { *norm = "arm"; } else if (str_startswith(machine, &WKSTR("mips"))) { if (strstr(machine->s, "64")) { *norm = "mips64"; } else { *norm = "mips"; } } else { const char *map[][2] = { { "amd64", "x86_64" }, { "x64", "x86_64" }, { "i86pc", "x86_64" }, 0 }; uint32_t i; for (i = 0; map[i][0]; ++i) { if (str_eql(&WKSTR(map[i][0]), machine)) { *norm = map[i][1]; break; } } } } static const char * machine_cpu_family(void) { const char *machine_cstr, *norm; const struct str *machine; machine_cpu_normalize_base(&machine_cstr, &norm); machine = &WKSTR(machine_cstr); if (norm) { goto done; } if (machine->s[0] == 'i' && str_endswith(machine, &WKSTR("86"))) { norm = "x86"; } else if (str_startswith(machine, &WKSTR("arm"))) { norm = "arm"; } else if (str_startswith(machine, &WKSTR("powerpc64")) || str_startswith(machine, &WKSTR("ppc64"))) { norm = "ppc64"; } else if (str_startswith(machine, &WKSTR("powerpc")) || str_startswith(machine, &WKSTR("ppc"))) { norm = "ppc"; } else { const char *map[][2] = { { "bepc", "x86" }, { "arm64", "aarch64" }, { "macppc", "ppc" }, { "power macintosh", "ppc" }, { "amd64", "x86_64" }, { "x64", "x86_64" }, { "i86pc", "x86_64" }, { "sun4u", "sparc64" }, { "sun4v", "sparc64" }, { "ip30", "mpis64" }, { "ip35", "mpis64" }, 0 }; uint32_t i; for (i = 0; map[i][0]; ++i) { if (str_eql(&WKSTR(map[i][0]), machine)) { norm = map[i][1]; break; } } } done: if (!norm) { norm = machine->s; } uint32_t i; for (i = 0; i < ARRAY_LEN(known_cpu_families); ++i) { if (strcmp(norm, known_cpu_families[i]) == 0) { return norm; } } LOG_W("returning unknown cpu family '%s'", machine->s); return norm; } static bool func_machine_cpu_family(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, machine_cpu_family()); return true; } uint32_t machine_cpu_address_bits(void) { const char *is_64_bit[] = { "aarch64", "alpha", "ia64", "loongarch64", "mips64", "ppc64", "riscv64", "s390x", "sparc64", "wasm64", "x86_64", }; const char *fam = machine_cpu_family(); uint32_t i; for (i = 0; i < ARRAY_LEN(is_64_bit); ++i) { if (strcmp(fam, is_64_bit[i]) == 0) { return 64; } } return 32; } static bool func_machine_cpu(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { const char *machine_cstr, *norm; if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } machine_cpu_normalize_base(&machine_cstr, &norm); if (!norm) { norm = machine_cstr; } *res = make_str(wk, norm); return true; } const struct func_impl_name impl_tbl_machine[] = { { "cpu", func_machine_cpu, tc_string }, { "cpu_family", func_machine_cpu_family, tc_string }, { "endian", func_machine_endian, tc_string }, { "system", func_machine_system, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/meson.c000066400000000000000000000312551441402162300165560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "coerce.h" #include "compilers.h" #include "error.h" #include "functions/build_target.h" #include "functions/common.h" #include "functions/meson.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/path.h" #include "version.h" static bool func_meson_get_compiler(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } enum compiler_language l; if (!s_to_compiler_language(get_cstr(wk, an[0].val), &l) || !obj_dict_geti(wk, current_project(wk)->compilers, l, res)) { interp_error(wk, an[0].node, "no compiler found for '%s'", get_cstr(wk, an[0].val)); return false; } return true; } static bool func_meson_project_name(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.name; return true; } static bool func_meson_project_license(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.license; return true; } static bool func_meson_project_version(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->cfg.version; return true; } static bool func_meson_version(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, muon_version.meson_compat); return true; } static bool func_meson_current_source_dir(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->cwd; return true; } static bool func_meson_current_build_dir(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->build_dir; return true; } static bool func_meson_project_source_root(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->source_root; return true; } static bool func_meson_project_build_root(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = current_project(wk)->build_root; return true; } static bool func_meson_global_source_root(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, wk->source_root); return true; } static bool func_meson_global_build_root(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, wk->build_root); return true; } static bool func_meson_is_subproject(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, wk->cur_project != 0); return true; } static bool func_meson_backend(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_str(wk, "ninja"); return true; } static bool func_meson_is_cross_build(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } static bool func_meson_is_unity(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } static bool func_meson_override_dependency(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_static, kw_native, // ignored }; struct args_kw akw[] = { [kw_static] = { "static", obj_bool }, [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } obj override_dict; if (akw[kw_static].set) { if (get_obj_bool(wk, akw[kw_static].val)) { override_dict = wk->dep_overrides_static; } else { override_dict = wk->dep_overrides_dynamic; } } else { switch (get_option_default_library(wk)) { case tgt_static_library: override_dict = wk->dep_overrides_static; break; default: override_dict = wk->dep_overrides_dynamic; break; } } obj_dict_set(wk, override_dict, an[0].val, an[1].val); return true; } static bool func_meson_override_find_program(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { tc_file | tc_external_program | tc_build_target | tc_custom_target }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } obj override; switch (get_obj_type(wk, an[1].val)) { case obj_build_target: case obj_custom_target: case obj_file: make_obj(wk, &override, obj_array); obj_array_push(wk, override, an[1].val); obj ver = 0; if (!current_project(wk)->cfg.no_version) { ver = current_project(wk)->cfg.version; } obj_array_push(wk, override, ver); break; case obj_external_program: override = an[1].val; break; default: UNREACHABLE; } obj_dict_set(wk, wk->find_program_overrides, an[0].val, override); return true; } struct process_script_commandline_ctx { uint32_t node; obj arr; uint32_t i; bool allow_not_built; bool make_deps_default; }; static enum iteration_result process_script_commandline_iter(struct workspace *wk, void *_ctx, obj val) { struct process_script_commandline_ctx *ctx = _ctx; obj str; enum obj_type t = get_obj_type(wk, val); switch (t) { case obj_string: if (ctx->i) { str = val; } else { const char *p = get_cstr(wk, val); if (path_is_absolute(p)) { str = val; } else { SBUF(path); path_join(wk, &path, get_cstr(wk, current_project(wk)->cwd), p); str = sbuf_into_str(wk, &path); } } break; case obj_custom_target: if (!ctx->allow_not_built) { goto type_error; } struct obj_custom_target *o = get_obj_custom_target(wk, val); if (ctx->make_deps_default) { o->flags |= custom_target_build_by_default; } if (!obj_array_foreach(wk, o->output, ctx, process_script_commandline_iter)) { return false; } goto cont; case obj_build_target: { if (!ctx->allow_not_built) { goto type_error; } struct obj_build_target *o = get_obj_build_target(wk, val); if (ctx->make_deps_default) { o->flags |= build_tgt_flag_build_by_default; } } //fallthrough case obj_external_program: case obj_file: if (!coerce_executable(wk, ctx->node, val, &str)) { return ir_err; } break; default: type_error: interp_error(wk, ctx->node, "invalid type for script commandline '%s'", obj_type_to_s(t)); return ir_err; } obj_array_push(wk, ctx->arr, str); cont: ++ctx->i; return ir_cont; } static bool func_meson_add_install_script(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_exe }, ARG_TYPE_NULL }; enum kwargs { kw_install_tag, // ignored kw_skip_if_destdir, // ignored }; struct args_kw akw[] = { [kw_install_tag] = { "install_tag", obj_string }, [kw_skip_if_destdir] = { "skip_if_destdir", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, .allow_not_built = true, .make_deps_default = true, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_iter)) { return false; } obj_array_push(wk, wk->install_scripts, ctx.arr); return true; } static bool func_meson_add_postconf_script(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_exe }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_iter)) { return false; } obj_array_push(wk, wk->postconf_scripts, ctx.arr); return true; } static bool func_meson_add_dist_script(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_exe }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } struct process_script_commandline_ctx ctx = { .node = an[0].node, .allow_not_built = true, }; make_obj(wk, &ctx.arr, obj_array); if (!obj_array_foreach_flat(wk, an[0].val, &ctx, process_script_commandline_iter)) { return false; } // TODO: uncomment when muon dist is implemented /* obj_array_push(wk, wk->dist_scripts, ctx.arr); */ return true; } static bool func_meson_get_cross_property(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } if (ao[0].set) { *res = ao[0].val; } else { interp_error(wk, an[0].node, "TODO: get cross property"); return false; } return true; } static bool func_meson_get_external_property(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; enum kwargs { kw_native, }; struct args_kw akw[] = { [kw_native] = { "native", obj_bool }, 0 }; if (!interp_args(wk, args_node, an, ao, akw)) { return false; } if (ao[0].set) { *res = ao[0].val; } else { interp_error(wk, an[0].node, "TODO: get external property"); return false; } return true; } static bool func_meson_can_run_host_binaries(struct workspace *wk, obj _, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, true); // TODO: can return false in cross compile return true; } static bool func_meson_add_devenv(struct workspace *wk, obj _, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } return true; } const struct func_impl_name impl_tbl_meson[] = { { "add_devenv", func_meson_add_devenv }, { "add_dist_script", func_meson_add_dist_script }, { "add_install_script", func_meson_add_install_script }, { "add_postconf_script", func_meson_add_postconf_script }, { "backend", func_meson_backend, tc_string }, { "build_root", func_meson_global_build_root, tc_string }, { "can_run_host_binaries", func_meson_can_run_host_binaries, tc_bool }, { "current_build_dir", func_meson_current_build_dir, tc_string }, { "current_source_dir", func_meson_current_source_dir, tc_string }, { "get_compiler", func_meson_get_compiler, tc_compiler }, { "get_cross_property", func_meson_get_cross_property, tc_any }, { "get_external_property", func_meson_get_external_property, tc_any }, { "global_build_root", func_meson_global_build_root, tc_string }, { "global_source_root", func_meson_global_source_root, tc_string }, { "has_exe_wrapper", func_meson_can_run_host_binaries, tc_bool }, { "is_cross_build", func_meson_is_cross_build, tc_bool }, { "is_subproject", func_meson_is_subproject, tc_bool }, { "is_unity", func_meson_is_unity, tc_bool }, { "override_dependency", func_meson_override_dependency }, { "override_find_program", func_meson_override_find_program }, { "project_build_root", func_meson_project_build_root, tc_string }, { "project_license", func_meson_project_license, tc_string }, { "project_name", func_meson_project_name, tc_string }, { "project_source_root", func_meson_project_source_root, tc_string }, { "project_version", func_meson_project_version, tc_string }, { "source_root", func_meson_global_source_root, tc_string }, { "version", func_meson_version, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/modules.c000066400000000000000000000052451441402162300171050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/common.h" #include "functions/modules.h" #include "functions/modules/fs.h" #include "functions/modules/keyval.h" #include "functions/modules/sourceset.h" #include "functions/modules/pkgconfig.h" #include "functions/modules/python.h" #include "log.h" const char *module_names[module_count] = { [module_fs] = "fs", [module_keyval] = "keyval", [module_pkgconfig] = "pkgconfig", [module_python3] = "python3", [module_python] = "python", [module_sourceset] = "sourceset", // unimplemented [module_cmake] = "cmake", [module_dlang] = "dlang", [module_gnome] = "gnome", [module_hotdoc] = "hotdoc", [module_i18n] = "i18n", [module_java] = "java", [module_modtest] = "modtest", [module_qt] = "qt", [module_qt4] = "qt4", [module_qt5] = "qt5", [module_qt6] = "qt6", [module_unstable_cuda] = "unstable-cuda", [module_unstable_external_project] = "unstable-external_project", [module_unstable_icestorm] = "unstable-icestorm", [module_unstable_rust] = "unstable-rust", [module_unstable_simd] = "unstable-simd", [module_unstable_wayland] = "unstable-wayland", [module_windows] = "windows", }; bool module_lookup(const char *name, enum module *res, bool *has_impl) { enum module i; for (i = 0; i < module_count; ++i) { if (strcmp(name, module_names[i]) == 0) { *res = i; *has_impl = i < module_unimplemented_separator; return true; } } return false; } static bool func_module_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, get_obj_module(wk, rcvr)->found); return true; } const struct func_impl_name *module_func_tbl[module_count][language_mode_count] = { [module_fs] = { impl_tbl_module_fs, impl_tbl_module_fs_internal }, [module_keyval] = { impl_tbl_module_keyval }, [module_pkgconfig] = { impl_tbl_module_pkgconfig }, [module_python3] = { impl_tbl_module_python3 }, [module_python] = { impl_tbl_module_python }, [module_sourceset] = { impl_tbl_module_sourceset }, }; const struct func_impl_name impl_tbl_module[] = { { "found", func_module_found, tc_bool, }, { NULL, NULL }, }; const struct func_impl_name * module_func_lookup(struct workspace *wk, const char *name, enum module mod) { if (strcmp(name, "found") == 0) { return &impl_tbl_module[0]; } if (!module_func_tbl[mod][wk->lang_mode]) { return NULL; } const struct func_impl_name *fi; if (!(fi = func_lookup(module_func_tbl[mod][wk->lang_mode], name))) { return NULL; } return fi; } muon-0.2.0/src/functions/modules/000077500000000000000000000000001441402162300167335ustar00rootroot00000000000000muon-0.2.0/src/functions/modules/fs.c000066400000000000000000000435231441402162300175160ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "args.h" #include "functions/common.h" #include "functions/kernel/custom_target.h" #include "functions/modules/fs.h" #include "lang/interpreter.h" #include "log.h" #include "platform/filesystem.h" #include "platform/path.h" #include "sha_256.h" enum fix_file_path_opts { fix_file_path_allow_file = 1 << 0, fix_file_path_expanduser = 1 << 1, fix_file_path_noabs = 1 << 2, }; static bool fix_file_path(struct workspace *wk, uint32_t err_node, obj path, enum fix_file_path_opts opts, struct sbuf *buf) { enum obj_type t = get_obj_type(wk, path); const struct str *ss; switch (t) { case obj_string: ss = get_str(wk, path); break; case obj_file: if (opts & fix_file_path_allow_file) { ss = get_str(wk, *get_obj_file(wk, path)); break; } // FALLTHROUGH default: interp_error(wk, err_node, "expected string%s, got %s", (opts & fix_file_path_allow_file) ? " or file" : "", obj_type_to_s(t)); return false; } if (str_has_null(ss)) { interp_error(wk, err_node, "path cannot contain null bytes"); return false; } else if (!ss->len) { interp_error(wk, err_node, "path cannot be empty"); return false; } if (path_is_absolute(ss->s)) { path_copy(wk, buf, ss->s); } else { if ((opts & fix_file_path_expanduser) && ss->s[0] == '~') { const char *home; if (!(home = fs_user_home())) { interp_error(wk, err_node, "failed to get user home directory"); return false; } path_join(wk, buf, home, &ss->s[1]); } else if (opts & fix_file_path_noabs) { path_copy(wk, buf, ss->s); } else { path_join(wk, buf, get_cstr(wk, current_project(wk)->cwd), ss->s); } } _path_normalize(wk, buf, true); return true; } typedef bool ((*fs_lookup_func)(const char *)); static bool func_module_fs_lookup_common(struct workspace *wk, uint32_t args_node, obj *res, fs_lookup_func lookup, enum fix_file_path_opts opts) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, opts, &path)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, lookup(path.buf)); return true; } static bool func_module_fs_exists(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_module_fs_lookup_common(wk, args_node, res, fs_exists, fix_file_path_expanduser); } static bool func_module_fs_is_file(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_module_fs_lookup_common(wk, args_node, res, fs_file_exists, fix_file_path_expanduser); } static bool func_module_fs_is_dir(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_module_fs_lookup_common(wk, args_node, res, fs_dir_exists, fix_file_path_expanduser); } static bool func_module_fs_is_symlink(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { return func_module_fs_lookup_common(wk, args_node, res, fs_symlink_exists, fix_file_path_allow_file | fix_file_path_expanduser); } static bool func_module_fs_parent(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_expanduser | fix_file_path_noabs, &path)) { return false; } SBUF(buf); path_dirname(wk, &buf, path.buf); *res = sbuf_into_str(wk, &buf); return true; } static bool func_module_fs_read(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; enum { kw_encoding, }; struct args_kw akw[] = { [kw_encoding] = { "encoding", obj_string }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (akw[kw_encoding].set) { if (!str_eql(get_str(wk, akw[kw_encoding].val), &WKSTR("utf-8"))) { interp_error(wk, akw[kw_encoding].node, "only 'utf-8' supported"); } } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_expanduser, &path)) { return false; } struct source src = { 0 }; if (!fs_read_entire_file(path.buf, &src)) { return false; } *res = make_strn(wk, src.src, src.len); fs_source_destroy(&src); return true; } static bool func_module_fs_is_absolute(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, path_is_absolute(get_cstr(wk, an[0].val))); return true; } static bool func_module_fs_expanduser(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_expanduser, &path)) { return false; } *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_name(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file, &path)) { return false; } SBUF(basename); path_basename(wk, &basename, path.buf); *res = sbuf_into_str(wk, &basename); return true; } static bool func_module_fs_stem(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file, &path)) { return false; } SBUF(basename); path_basename(wk, &basename, path.buf); char *dot; if ((dot = strrchr(basename.buf, '.'))) { *dot = 0; basename.len = strlen(basename.buf); } *res = sbuf_into_str(wk, &basename); return true; } static bool func_module_as_posix(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const char *path = get_cstr(wk, an[0].val), *p; SBUF(buf); for (p = path; *p; ++p) { if (*p == '\\') { sbuf_push(wk, &buf, '/'); if (*(p + 1) == '\\') { ++p; } } else { sbuf_push(wk, &buf, *p); } } *res = sbuf_into_str(wk, &buf); return true; } static bool func_module_replace_suffix(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_noabs, &path)) { return false; } char *dot; if ((dot = strrchr(path.buf, '.'))) { *dot = 0; path.len = strlen(path.buf); } sbuf_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_hash(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } if (!str_eql(get_str(wk, an[1].val), &WKSTR("sha256"))) { interp_error(wk, an[1].node, "only sha256 is supported"); return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file, &path)) { return false; } struct source src = { 0 }; if (!fs_read_entire_file(path.buf, &src)) { return false; } uint8_t hash[32] = { 0 }; calc_sha_256(hash, src.src, src.len); // TODO: other hash algos char buf[65] = { 0 }; uint32_t i, bufi = 0; for (i = 0; i < 32; ++i) { snprintf(&buf[bufi], 3, "%x", hash[i]); bufi += 2; } *res = make_str(wk, buf); fs_source_destroy(&src); return true; } static bool func_module_fs_size(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file, &path)) { return false; } uint64_t size; FILE *f; if (!(f = fs_fopen(path.buf, "rb"))) { return false; } else if (!fs_fsize(f, &size)) { return false; } else if (!fs_fclose(f)) { return false; } assert(size < INT64_MAX); make_obj(wk, res, obj_number); set_obj_number(wk, *res, size); return true; } static bool func_module_fs_is_samepath(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path1); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file, &path1)) { return false; } SBUF(path2); if (!fix_file_path(wk, an[1].node, an[1].val, fix_file_path_allow_file, &path2)) { return false; } // TODO: handle symlinks make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, strcmp(path1.buf, path2.buf) == 0); return true; } static bool func_module_fs_copyfile(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_string }, ARG_TYPE_NULL }; enum { kw_install, kw_install_dir, kw_install_tag, kw_install_mode, }; struct args_kw akw[] = { [kw_install] = { "install", obj_bool }, [kw_install_dir] = { "install_dir", ARG_TYPE_ARRAY_OF | tc_string | tc_bool }, [kw_install_tag] = { "install_tag", tc_string }, // TODO [kw_install_mode] = { "install_mode", tc_install_mode_kw }, 0 }; if (!interp_args(wk, args_node, an, ao, akw)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_expanduser, &path)) { return false; } obj output; if (ao[0].set) { output = ao[0].val; } else { SBUF(dest); path_basename(wk, &dest, path.buf); output = sbuf_into_str(wk, &dest); } obj command; make_obj(wk, &command, obj_array); push_args_null_terminated(wk, command, (char *const []){ (char *)wk->argv0, "internal", "eval", "-e", "copyfile.meson", "@INPUT@", "@OUTPUT@", NULL, }); struct make_custom_target_opts opts = { .name = make_str(wk, "copyfile"), .input_node = an[0].node, .output_node = ao[1].node, .input_orig = an[0].val, .output_orig = output, .output_dir = get_cstr(wk, current_project(wk)->build_dir), .command_orig = command, }; if (!make_custom_target(wk, &opts, res)) { return false; } obj_array_push(wk, current_project(wk)->targets, *res); return true; } const struct func_impl_name impl_tbl_module_fs[] = { { "as_posix", func_module_as_posix, tc_string, true }, { "copyfile", func_module_fs_copyfile, tc_custom_target }, { "exists", func_module_fs_exists, tc_bool }, { "expanduser", func_module_fs_expanduser, tc_string }, { "hash", func_module_fs_hash, tc_string }, { "is_absolute", func_module_fs_is_absolute, tc_bool, true }, { "is_dir", func_module_fs_is_dir, tc_bool }, { "is_file", func_module_fs_is_file, tc_bool }, { "is_samepath", func_module_fs_is_samepath, tc_bool }, { "is_symlink", func_module_fs_is_symlink, tc_bool }, { "name", func_module_fs_name, tc_string, true }, { "parent", func_module_fs_parent, tc_string, true }, { "read", func_module_fs_read, tc_string }, { "replace_suffix", func_module_replace_suffix, tc_string, true }, { "size", func_module_fs_size, tc_number, }, { "stem", func_module_fs_stem, tc_string, true }, { NULL, NULL }, }; static bool func_module_fs_write(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_expanduser, &path)) { return false; } const struct str *ss = get_str(wk, an[1].val); if (!fs_write(path.buf, (uint8_t *)ss->s, ss->len)) { return false; } return true; } static bool func_module_fs_copy(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string | tc_file }, { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); if (!fix_file_path(wk, an[0].node, an[0].val, fix_file_path_allow_file | fix_file_path_expanduser, &path)) { return false; } if (!fs_copy_file(path.buf, get_cstr(wk, an[1].val))) { return false; } return true; } static bool func_module_fs_cwd(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } SBUF(cwd); path_cwd(wk, &cwd); *res = sbuf_into_str(wk, &cwd); return true; } static bool func_module_fs_make_absolute(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); path_make_absolute(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_mkdir(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } return fs_mkdir(get_cstr(wk, an[0].val)); } static bool func_module_fs_relative_to(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const char *p1 = get_cstr(wk, an[0].val), *p2 = get_cstr(wk, an[1].val); if (!path_is_absolute(p1)) { interp_error(wk, an[0].node, "base path '%s' is not absolute", p1); return false; } else if (!path_is_absolute(p2)) { interp_error(wk, an[1].node, "path '%s' is not absolute", p2); return false; } SBUF(path); path_relative_to(wk, &path, p1, p2); *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_is_basename(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, path_is_basename(get_cstr(wk, an[0].val))); return true; } static bool func_module_fs_without_ext(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); path_without_ext(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_is_subpath(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, path_is_subpath(get_cstr(wk, an[0].val), get_cstr(wk, an[1].val))); return true; } static bool func_module_fs_add_suffix(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); path_copy(wk, &path, get_cstr(wk, an[0].val)); sbuf_pushs(wk, &path, get_cstr(wk, an[1].val)); *res = sbuf_into_str(wk, &path); return true; } static bool func_module_fs_executable(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } SBUF(path); path_executable(wk, &path, get_cstr(wk, an[0].val)); *res = sbuf_into_str(wk, &path); return true; } const struct func_impl_name impl_tbl_module_fs_internal[] = { { "as_posix", func_module_as_posix, tc_string, true }, { "copyfile", func_module_fs_copyfile, }, { "exists", func_module_fs_exists, tc_bool }, { "expanduser", func_module_fs_expanduser, tc_string }, { "hash", func_module_fs_hash, tc_string }, { "is_absolute", func_module_fs_is_absolute, tc_bool, true }, { "is_dir", func_module_fs_is_dir, tc_bool }, { "is_file", func_module_fs_is_file, tc_bool }, { "is_samepath", func_module_fs_is_samepath, tc_bool }, { "is_symlink", func_module_fs_is_symlink, tc_bool }, { "name", func_module_fs_name, tc_string, true }, { "parent", func_module_fs_parent, tc_string, true }, { "read", func_module_fs_read, tc_string }, { "replace_suffix", func_module_replace_suffix, tc_string, true }, { "size", func_module_fs_size, tc_number, }, { "stem", func_module_fs_stem, tc_string, true }, // non-standard muon extensions { "add_suffix", func_module_fs_add_suffix, tc_string, true }, { "copy", func_module_fs_copy, .fuzz_unsafe = true }, { "cwd", func_module_fs_cwd, tc_string }, { "executable", func_module_fs_executable, tc_string, true }, { "is_basename", func_module_fs_is_basename, tc_string, true }, { "is_subpath", func_module_fs_is_subpath, tc_string, true }, { "make_absolute", func_module_fs_make_absolute, tc_string }, { "mkdir", func_module_fs_mkdir, .fuzz_unsafe = true }, { "relative_to", func_module_fs_relative_to, tc_string, true }, { "without_ext", func_module_fs_without_ext, tc_string, true }, { "write", func_module_fs_write, .fuzz_unsafe = true }, { NULL, NULL }, }; muon-0.2.0/src/functions/modules/keyval.c000066400000000000000000000030111441402162300203650ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "formats/ini.h" #include "functions/common.h" #include "functions/modules/keyval.h" #include "platform/filesystem.h" #include "platform/mem.h" struct keyval_parse_ctx { struct workspace *wk; obj dict; }; static bool keyval_parse_cb(void *_ctx, struct source *src, const char *sect, const char *k, const char *v, uint32_t line) { struct keyval_parse_ctx *ctx = _ctx; obj_dict_set(ctx->wk, ctx->dict, make_str(ctx->wk, k), make_str(ctx->wk, v)); return true; } static bool func_module_keyval_load(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { bool ret = false; struct args_norm an[] = { { tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const char *path = NULL; switch (get_obj_type(wk, an[0].val)) { case obj_file: path = get_file_path(wk, an[0].val); break; case obj_string: path = get_cstr(wk, an[0].val); break; default: UNREACHABLE; } make_obj(wk, res, obj_dict); struct keyval_parse_ctx ctx = { .wk = wk, .dict = *res, }; struct source src = { 0 }; char *buf = NULL; if (!keyval_parse(path, &src, &buf, keyval_parse_cb, &ctx)) { goto ret; } ret = true; ret: fs_source_destroy(&src); if (buf) { z_free(buf); } return ret; } const struct func_impl_name impl_tbl_module_keyval[] = { { "load", func_module_keyval_load, tc_dict, }, { NULL, NULL }, }; muon-0.2.0/src/functions/modules/pkgconfig.c000066400000000000000000000624151441402162300210560ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "args.h" #include "error.h" #include "functions/custom_target.h" #include "functions/file.h" #include "install.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/path.h" enum pkgconf_visibility { pkgconf_visibility_pub, pkgconf_visibility_priv, }; struct pkgconf_file { // strings obj name, description, url, version; // arrays of string obj cflags, conflicts; obj builtin_dir_variables, variables; obj reqs[2], libs[2]; obj exclude; bool libs_contains_internal[2]; bool dataonly; }; enum module_pkgconf_diropt { module_pkgconf_diropt_prefix, module_pkgconf_diropt_bindir, module_pkgconf_diropt_datadir, module_pkgconf_diropt_includedir, module_pkgconf_diropt_infodir, module_pkgconf_diropt_libdir, module_pkgconf_diropt_libexecdir, module_pkgconf_diropt_localedir, module_pkgconf_diropt_localstatedir, module_pkgconf_diropt_mandir, module_pkgconf_diropt_sbindir, module_pkgconf_diropt_sharedstatedir, module_pkgconf_diropt_sysconfdir, }; static struct { const char *const name, *const optname; bool refd, added; } module_pkgconf_diropts[] = { [module_pkgconf_diropt_prefix] = { "${prefix}", "prefix" }, [module_pkgconf_diropt_bindir] = { "${bindir}", "bindir" }, [module_pkgconf_diropt_datadir] = { "${datadir}", "datadir" }, [module_pkgconf_diropt_includedir] = { "${includedir}", "includedir" }, [module_pkgconf_diropt_infodir] = { "${infodir}", "infodir" }, [module_pkgconf_diropt_libdir] = { "${libdir}", "libdir" }, [module_pkgconf_diropt_libexecdir] = { "${libexecdir}", "libexecdir" }, [module_pkgconf_diropt_localedir] = { "${localedir}", "localedir" }, [module_pkgconf_diropt_localstatedir] = { "${localstatedir}", "localstatedir" }, [module_pkgconf_diropt_mandir] = { "${mandir}", "mandir" }, [module_pkgconf_diropt_sbindir] = { "${sbindir}", "sbindir" }, [module_pkgconf_diropt_sharedstatedir] = { "${sharedstatedir}", "sharedstatedir" }, [module_pkgconf_diropt_sysconfdir] = { "${sysconfdir}", "sysconfdir" }, }; static enum iteration_result add_subdirs_includes_iter(struct workspace *wk, void *_ctx, obj val) { obj *cflags = _ctx; if (str_eql(get_str(wk, val), &WKSTR("."))) { obj_array_push(wk, *cflags, make_str(wk, "-I${includedir}")); } else { SBUF(path); path_join(wk, &path, "-I${includedir}", get_cstr(wk, val)); obj_array_push(wk, *cflags, sbuf_into_str(wk, &path)); } return ir_cont; } static bool module_pkgconf_lib_to_lname(struct workspace *wk, obj lib, obj *res) { SBUF(basename); const char *str; switch (get_obj_type(wk, lib)) { case obj_string: str = get_cstr(wk, lib); break; case obj_file: { path_basename(wk, &basename, get_file_path(wk, lib)); char *dot; if ((dot = strrchr(basename.buf, '.'))) { *dot = '\0'; } str = basename.buf; break; } default: UNREACHABLE; } if (str[0] == '-') { *res = make_str(wk, str); return true; } struct str s = WKSTR(str); if (str_startswith(&s, &WKSTR("-l"))) { s.len -= 2; s.s += 2; } else if (str_startswith(&s, &WKSTR("lib"))) { s.len -= 3; s.s += 3; } *res = make_strf(wk, "-l%.*s", s.len, s.s); return true; } struct module_pkgconf_process_reqs_iter_ctx { uint32_t err_node; obj dest; }; static enum iteration_result module_pkgconf_process_reqs_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_process_reqs_iter_ctx *ctx = _ctx; switch (get_obj_type(wk, val)) { case obj_string: obj_array_push(wk, ctx->dest, val); break; case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); if (!tgt->generated_pc) { interp_error(wk, ctx->err_node, "build target has no associated pc file"); return ir_err; } obj_array_push(wk, ctx->dest, tgt->generated_pc); break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found) || (dep->type == dependency_type_threads)) { return ir_cont; } if (dep->type != dependency_type_pkgconf) { interp_error(wk, ctx->err_node, "dependency not from pkgconf"); return ir_err; } obj_array_push(wk, ctx->dest, dep->name); // TODO: handle version req break; } default: interp_error(wk, ctx->err_node, "invalid type for pkgconf require %s", obj_type_to_s(get_obj_type(wk, val))); return ir_err; } return ir_cont; } static bool module_pkgconf_process_reqs(struct workspace *wk, uint32_t err_node, obj reqs, obj dest) { struct module_pkgconf_process_reqs_iter_ctx ctx = { .err_node = err_node, .dest = dest, }; if (!obj_array_foreach(wk, reqs, &ctx, module_pkgconf_process_reqs_iter)) { return false; } return true; } struct module_pkgconf_process_libs_iter_ctx { uint32_t err_node; struct pkgconf_file *pc; enum pkgconf_visibility vis; bool link_whole; }; static bool module_pkgconf_process_libs(struct workspace *wk, uint32_t err_node, obj src, struct pkgconf_file *pc, enum pkgconf_visibility vis, bool link_whole); static enum iteration_result str_to_file_iter(struct workspace *wk, void *_ctx, obj v) { obj *arr = _ctx; obj f; make_obj(wk, &f, obj_file); *get_obj_file(wk, f) = v; obj_array_push(wk, *arr, f); return ir_cont; } static enum iteration_result module_pkgconf_process_libs_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_process_libs_iter_ctx *ctx = _ctx; /* obj_fprintf(wk, log_file(), "%o\n", val); */ switch (get_obj_type(wk, val)) { case obj_string: { obj lib; if (!module_pkgconf_lib_to_lname(wk, val, &lib)) { return ir_err; } obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } case obj_file: { if (!file_is_linkable(wk, val)) { interp_error(wk, ctx->err_node, "non linkable file %o among libraries", val); return ir_err; } if (path_is_subpath(wk->source_root, get_file_path(wk, val)) || path_is_subpath(wk->build_root, get_file_path(wk, val))) { ctx->pc->libs_contains_internal[ctx->vis] = true; } obj lib; if (!module_pkgconf_lib_to_lname(wk, val, &lib)) { return ir_err; } obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } case obj_both_libs: val = get_obj_both_libs(wk, val)->dynamic_lib; /* fallthrough */ case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, val); if (tgt->generated_pc) { obj_array_push(wk, ctx->pc->reqs[ctx->vis], tgt->generated_pc); } else { if (tgt->type == tgt_executable) { interp_error(wk, ctx->err_node, "invalid build_target type"); return ir_err; } if (tgt->dep.raw.deps) { if (!module_pkgconf_process_libs(wk, ctx->err_node, tgt->dep.raw.deps, ctx->pc, pkgconf_visibility_priv, false)) { return ir_err; } } const enum pkgconf_visibility link_vis = tgt->type == tgt_static_library ? pkgconf_visibility_pub : pkgconf_visibility_priv; if (tgt->dep.raw.link_with) { if (!module_pkgconf_process_libs(wk, ctx->err_node, tgt->dep.raw.link_with, ctx->pc, link_vis, false)) { return ir_err; } } if (tgt->dep.raw.link_whole) { if (!module_pkgconf_process_libs(wk, ctx->err_node, tgt->dep.raw.link_whole, ctx->pc, link_vis, true)) { return ir_err; } } obj lib; if (!module_pkgconf_lib_to_lname(wk, tgt->name, &lib)) { return ir_err; } if (ctx->link_whole) { obj_array_push(wk, ctx->pc->exclude, lib); return ir_cont; } else if ((tgt->type == tgt_static_library && !(tgt->flags & build_tgt_flag_installed))) { return ir_cont; } ctx->pc->libs_contains_internal[ctx->vis] = true; obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); } break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, val); if (!(dep->flags & dep_flag_found)) { return ir_cont; } switch (dep->type) { case dependency_type_declared: { // TODO: I'm pretty sure this doesn't obey partial // dependency semantics if this is a sub dependency of // a partial dep with compile_args: false if (dep->dep.compile_args) { obj_array_extend(wk, ctx->pc->cflags, dep->dep.compile_args); } if (dep->dep.raw.link_with) { if (!module_pkgconf_process_libs(wk, ctx->err_node, dep->dep.raw.link_with, ctx->pc, ctx->vis, false)) { return ir_err; } } if (dep->dep.raw.link_whole) { if (!module_pkgconf_process_libs(wk, ctx->err_node, dep->dep.raw.link_whole, ctx->pc, ctx->vis, true)) { return ir_err; } } if (dep->dep.raw.deps) { if (!module_pkgconf_process_libs(wk, ctx->err_node, dep->dep.raw.deps, ctx->pc, pkgconf_visibility_priv, false)) { return ir_err; } } break; } case dependency_type_pkgconf: obj_array_push(wk, ctx->pc->reqs[ctx->vis], dep->name); // TODO: handle version req break; case dependency_type_threads: obj_array_push(wk, ctx->pc->libs[pkgconf_visibility_priv], make_str(wk, "-pthread")); break; case dependency_type_external_library: { obj link_with_files; make_obj(wk, &link_with_files, obj_array); obj_array_foreach(wk, dep->dep.link_with, &link_with_files, str_to_file_iter); if (!module_pkgconf_process_libs(wk, ctx->err_node, link_with_files, ctx->pc, ctx->vis, false)) { return ir_err; } break; } case dependency_type_appleframeworks: // TODO: actually add correct -framework arguments break; } break; } case obj_custom_target: { if (!custom_target_is_linkable(wk, val)) { interp_error(wk, ctx->err_node, "non linkable custom target %o among libraries", val); return ir_err; } struct obj_custom_target *tgt = get_obj_custom_target(wk, val); obj out; obj_array_index(wk, tgt->output, 0, &out); if (str_endswith(get_str(wk, *get_obj_file(wk, out)), &WKSTR(".a"))) { return ir_cont; } obj lib; if (!module_pkgconf_lib_to_lname(wk, out, &lib)) { return ir_err; } ctx->pc->libs_contains_internal[ctx->vis] = true; obj_array_push(wk, ctx->pc->libs[ctx->vis], lib); break; } default: interp_error(wk, ctx->err_node, "invalid type for pkgconf library %s", obj_type_to_s(get_obj_type(wk, val))); return ir_err; } return ir_cont; } static bool module_pkgconf_process_libs(struct workspace *wk, uint32_t err_node, obj src, struct pkgconf_file *pc, enum pkgconf_visibility vis, bool link_whole) { struct module_pkgconf_process_libs_iter_ctx ctx = { .pc = pc, .err_node = err_node, .vis = vis, .link_whole = link_whole, }; if (get_obj_type(wk, src) == obj_array) { if (!obj_array_foreach(wk, src, &ctx, module_pkgconf_process_libs_iter)) { return false; } } else { if (module_pkgconf_process_libs_iter(wk, &ctx, src) == ir_err) { return false; } } return true; } static bool module_pkgconf_declare_var(struct workspace *wk, uint32_t err_node, bool escape, bool skip_reserved_check, const struct str *key, const struct str *val, obj dest) { if (!skip_reserved_check) { const char *reserved[] = { "prefix", "libdir", "includedir", NULL }; uint32_t i; for (i = 0; reserved[i]; ++i) { if (str_eql(key, &WKSTR(reserved[i]))) { interp_error(wk, err_node, "variable %s is reserved", reserved[i]); return false; } } for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { if (str_eql(key, &WKSTR(module_pkgconf_diropts[i].optname))) { module_pkgconf_diropts[i].added = true; } if (module_pkgconf_diropts[i].refd) { continue; } if (str_startswith(val, &WKSTR(module_pkgconf_diropts[i].name))) { module_pkgconf_diropts[module_pkgconf_diropt_prefix].refd = true; // prefix module_pkgconf_diropts[i].refd = true; } } } SBUF(esc); const char *esc_val; if (escape) { pkgconf_escape(wk, &esc, val->s); esc_val = esc.buf; } else { esc_val = val->s; } obj_array_push(wk, dest, make_strf(wk, "%.*s=%s", key->len, key->s, esc_val)); return true; } static void module_pkgconf_declare_builtin_dir_var(struct workspace *wk, const char *opt, obj dest) { obj val, valstr; get_option_value(wk, current_project(wk), opt, &val); if (strcmp(opt, "prefix") == 0) { valstr = val; } else { valstr = make_strf(wk, "${prefix}/%s", get_cstr(wk, val)); } module_pkgconf_declare_var(wk, 0, true, true, &WKSTR(opt), get_str(wk, valstr), dest); } struct module_pkgconf_process_vars_ctx { uint32_t err_node; bool escape, dataonly; obj dest; }; static enum iteration_result module_pkgconf_process_vars_array_iter(struct workspace *wk, void *_ctx, obj v) { struct module_pkgconf_process_vars_ctx *ctx = _ctx; const struct str *src = get_str(wk, v); const char *sep; if (!(sep = strchr(src->s, '='))) { interp_error(wk, ctx->err_node, "invalid variable string, missing '='"); return ir_err; } struct str key = { .s = src->s, .len = sep - src->s }; struct str val = { .s = src->s + (key.len + 1), .len = src->len - (key.len + 1), }; if (!module_pkgconf_declare_var(wk, ctx->err_node, ctx->escape, ctx->dataonly, &key, &val, ctx->dest)) { return ir_err; } return ir_cont; } static enum iteration_result module_pkgconf_process_vars_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct module_pkgconf_process_vars_ctx *ctx = _ctx; if (!module_pkgconf_declare_var(wk, ctx->err_node, ctx->escape, ctx->dataonly, get_str(wk, key), get_str(wk, val), ctx->dest)) { return ir_err; } return ir_cont; } static bool module_pkgconf_process_vars(struct workspace *wk, uint32_t err_node, bool escape, bool dataonly, obj vars, obj dest) { struct module_pkgconf_process_vars_ctx ctx = { .err_node = err_node, .escape = escape, .dataonly = dataonly, .dest = dest, }; switch (get_obj_type(wk, vars)) { case obj_string: if (module_pkgconf_process_vars_array_iter(wk, &ctx, vars) == ir_err) { return false; } break; case obj_array: if (!obj_array_foreach(wk, vars, &ctx, module_pkgconf_process_vars_array_iter)) { return false; } break; case obj_dict: if (!obj_dict_foreach(wk, vars, &ctx, module_pkgconf_process_vars_dict_iter)) { return false; } break; default: interp_error(wk, err_node, "invalid type for variables, expected array or dict"); return false; } return true; } static bool module_pkgconf_prepend_libdir(struct workspace *wk, struct args_kw *install_dir_opt, obj *libs) { obj libdir; const char *path; if (install_dir_opt->set) { SBUF(rel); obj pre; get_option_value(wk, current_project(wk), "prefix", &pre); const char *install_dir = get_cstr(wk, install_dir_opt->val), *prefix = get_cstr(wk, pre); if (path_is_subpath(prefix, install_dir)) { path_relative_to(wk, &rel, prefix, install_dir); path = rel.buf; } else if (path_is_absolute(install_dir)) { interp_error(wk, install_dir_opt->val, "absolute install dir path not a subdir of prefix"); return false; } else { path = install_dir; } libdir = make_strf(wk, "-L${prefix}/%s", path); } else { libdir = make_strf(wk, "-L${libdir}"); } obj arr; make_obj(wk, &arr, obj_array); obj_array_push(wk, arr, libdir); obj_array_extend_nodup(wk, arr, *libs); *libs = arr; return true; } struct module_pkgconf_remove_dups_ctx { obj exclude; obj res; }; enum iteration_result module_pkgconf_remove_dups_iter(struct workspace *wk, void *_ctx, obj val) { struct module_pkgconf_remove_dups_ctx *ctx = _ctx; if (obj_array_in(wk, ctx->exclude, val)) { return ir_cont; } obj_array_push(wk, ctx->exclude, val); obj_array_push(wk, ctx->res, val); return ir_cont; } static void module_pkgconf_remove_dups(struct workspace *wk, obj *list, obj exclude) { obj arr; make_obj(wk, &arr, obj_array); struct module_pkgconf_remove_dups_ctx ctx = { .exclude = exclude, .res = arr, }; obj_array_foreach(wk, *list, &ctx, module_pkgconf_remove_dups_iter); *list = ctx.res; } static bool module_pkgconf_write(struct workspace *wk, const char *path, struct pkgconf_file *pc) { FILE *f; if (!(f = fs_fopen(path, "wb"))) { return false; } if (get_obj_array(wk, pc->builtin_dir_variables)->len) { obj str; obj_array_join(wk, false, pc->builtin_dir_variables, make_str(wk, "\n"), &str); fputs(get_cstr(wk, str), f); fputc('\n', f); } if (get_obj_array(wk, pc->variables)->len) { fputc('\n', f); obj str; obj_array_join(wk, false, pc->variables, make_str(wk, "\n"), &str); fputs(get_cstr(wk, str), f); fputc('\n', f); } fputc('\n', f); fprintf(f, "Name: %s\n", get_cstr(wk, pc->name)); fprintf(f, "Description: %s\n", get_cstr(wk, pc->description)); if (pc->url) { fprintf(f, "URL: %s\n", get_cstr(wk, pc->url)); } fprintf(f, "Version: %s\n", get_cstr(wk, pc->version)); if (get_obj_array(wk, pc->reqs[pkgconf_visibility_pub])->len) { obj str; obj_array_join(wk, false, pc->reqs[pkgconf_visibility_pub], make_str(wk, ", "), &str); fprintf(f, "Requires: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->reqs[pkgconf_visibility_priv])->len) { obj str; obj_array_join(wk, false, pc->reqs[pkgconf_visibility_priv], make_str(wk, ", "), &str); fprintf(f, "Requires.private: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->libs[pkgconf_visibility_pub])->len) { obj str; obj_array_join(wk, false, pc->libs[pkgconf_visibility_pub], make_str(wk, " "), &str); fprintf(f, "Libs: %s\n", get_cstr(wk, str)); } if (get_obj_array(wk, pc->libs[pkgconf_visibility_priv])->len) { obj str; obj_array_join(wk, false, pc->libs[pkgconf_visibility_priv], make_str(wk, " "), &str); fprintf(f, "Libs.private: %s\n", get_cstr(wk, str)); } if (!pc->dataonly && get_obj_array(wk, pc->cflags)->len) { fprintf(f, "Cflags: %s\n", get_cstr(wk, join_args_pkgconf(wk, pc->cflags))); } if (!fs_fclose(f)) { return false; } return true; } static bool func_module_pkgconfig_generate(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { uint32_t i; struct args_norm ao[] = { { tc_both_libs | tc_build_target }, ARG_TYPE_NULL }; enum kwargs { kw_name, kw_description, kw_extra_cflags, kw_filebase, kw_install_dir, kw_libraries, kw_libraries_private, kw_subdirs, kw_requires, kw_requires_private, kw_url, kw_variables, kw_unescaped_variables, kw_uninstalled_variables, // TODO kw_unescaped_uninstalled_variables, // TODO kw_version, kw_dataonly, kw_conflicts, }; const type_tag tc_library = tc_string | tc_file | tc_build_target | tc_dependency | tc_custom_target | tc_both_libs, tc_requires = tc_string | tc_build_target | tc_dependency | tc_both_libs; struct args_kw akw[] = { [kw_name] = { "name", obj_string }, [kw_description] = { "description", obj_string }, [kw_extra_cflags] = { "extra_cflags", ARG_TYPE_ARRAY_OF | obj_string }, [kw_filebase] = { "filebase", obj_string }, [kw_install_dir] = { "install_dir", obj_string }, [kw_libraries] = { "libraries", ARG_TYPE_ARRAY_OF | tc_library }, [kw_libraries_private] = { "libraries_private", ARG_TYPE_ARRAY_OF | tc_library }, [kw_subdirs] = { "subdirs", ARG_TYPE_ARRAY_OF | obj_string }, [kw_requires] = { "requires", ARG_TYPE_ARRAY_OF | tc_requires }, [kw_requires_private] = { "requires_private", ARG_TYPE_ARRAY_OF | tc_requires }, [kw_url] = { "url", obj_string }, [kw_variables] = { "variables", tc_string | tc_array | tc_dict }, [kw_unescaped_variables] = { "unescaped_variables", tc_string | tc_array | tc_dict }, [kw_uninstalled_variables] = { "uninstalled_variables", tc_string | tc_array | tc_dict }, [kw_unescaped_uninstalled_variables] = { "unescaped_uninstalled_variables", tc_string | tc_array | tc_dict }, [kw_version] = { "version", obj_string }, [kw_dataonly] = { "dataonly", obj_bool }, [kw_conflicts] = { "conflicts", ARG_TYPE_ARRAY_OF | obj_string }, 0 }; if (!interp_args(wk, args_node, NULL, ao, akw)) { return false; } if (!ao[0].set && !akw[kw_name].set) { interp_error(wk, args_node, "you must either pass a library, " "or the name keyword"); return false; } struct pkgconf_file pc = { .url = akw[kw_url].val, .conflicts = akw[kw_conflicts].val, .dataonly = akw[kw_dataonly].set ? get_obj_bool(wk, akw[kw_dataonly].val) : false, }; for (i = 0; i < 2; ++i) { make_obj(wk, &pc.libs[i], obj_array); make_obj(wk, &pc.reqs[i], obj_array); } make_obj(wk, &pc.cflags, obj_array); make_obj(wk, &pc.variables, obj_array); make_obj(wk, &pc.builtin_dir_variables, obj_array); make_obj(wk, &pc.exclude, obj_array); obj mainlib = 0; if (ao[0].set) { switch (get_obj_type(wk, ao[0].val)) { case obj_both_libs: { mainlib = get_obj_both_libs(wk, ao[0].val)->dynamic_lib; break; } case obj_build_target: mainlib = ao[0].val; break; default: assert(false && "unreachable"); } } if (akw[kw_name].set) { pc.name = akw[kw_name].val; } else if (ao[0].set) { pc.name = get_obj_build_target(wk, mainlib)->name; } if (akw[kw_description].set) { pc.description = akw[kw_description].val; } else if (mainlib) { pc.description = make_strf(wk, "%s: %s", get_cstr(wk, current_project(wk)->cfg.name), get_cstr(wk, pc.name)); } if (akw[kw_version].set) { pc.version = akw[kw_version].val; } else { pc.version = current_project(wk)->cfg.version; } /* cflags include dirs */ if (akw[kw_subdirs].set) { if (!obj_array_foreach(wk, akw[kw_subdirs].val, &pc.cflags, add_subdirs_includes_iter)) { return false; } } else { obj_array_push(wk, pc.cflags, make_str(wk, "-I${includedir}")); } if (mainlib) { if (!module_pkgconf_process_libs(wk, ao[0].node, mainlib, &pc, pkgconf_visibility_pub, false)) { return false; } } if (akw[kw_libraries].set) { if (!module_pkgconf_process_libs(wk, akw[kw_libraries].node, akw[kw_libraries].val, &pc, pkgconf_visibility_pub, false)) { return false; } } if (akw[kw_libraries_private].set) { if (!module_pkgconf_process_libs(wk, akw[kw_libraries_private].node, akw[kw_libraries_private].val, &pc, pkgconf_visibility_priv, false)) { return false; } } module_pkgconf_remove_dups(wk, &pc.reqs[pkgconf_visibility_pub], pc.exclude); module_pkgconf_remove_dups(wk, &pc.libs[pkgconf_visibility_pub], pc.exclude); module_pkgconf_remove_dups(wk, &pc.reqs[pkgconf_visibility_priv], pc.exclude); module_pkgconf_remove_dups(wk, &pc.libs[pkgconf_visibility_priv], pc.exclude); for (i = 0; i < 2; ++i) { if (get_obj_array(wk, pc.libs[i])->len && pc.libs_contains_internal[i]) { if (!module_pkgconf_prepend_libdir(wk, &akw[kw_install_dir], &pc.libs[i])) { return false; } } } if (akw[kw_requires].set) { if (!module_pkgconf_process_reqs(wk, akw[kw_requires].node, akw[kw_requires].val, pc.reqs[pkgconf_visibility_pub])) { return false; } } if (akw[kw_requires_private].set) { if (!module_pkgconf_process_reqs(wk, akw[kw_requires_private].node, akw[kw_requires_private].val, pc.reqs[pkgconf_visibility_priv])) { return false; } } if (akw[kw_extra_cflags].set) { obj_array_extend(wk, pc.cflags, akw[kw_extra_cflags].val); } { // variables for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { module_pkgconf_diropts[i].refd = false; } if (!pc.dataonly) { module_pkgconf_diropts[module_pkgconf_diropt_prefix].refd = true; module_pkgconf_diropts[module_pkgconf_diropt_includedir].refd = true; if (get_obj_array(wk, pc.libs[0])->len || get_obj_array(wk, pc.libs[1])->len ) { module_pkgconf_diropts[module_pkgconf_diropt_libdir].refd = true; } } if (akw[kw_variables].set) { if (!module_pkgconf_process_vars(wk, akw[kw_variables].node, true, pc.dataonly, akw[kw_variables].val, pc.variables)) { return false; } } if (akw[kw_unescaped_variables].set) { if (!module_pkgconf_process_vars(wk, akw[kw_unescaped_variables].node, false, pc.dataonly, akw[kw_unescaped_variables].val, pc.variables)) { return false; } } for (i = 0; i < ARRAY_LEN(module_pkgconf_diropts); ++i) { if (module_pkgconf_diropts[i].refd && !module_pkgconf_diropts[i].added) { module_pkgconf_declare_builtin_dir_var(wk, module_pkgconf_diropts[i].optname, pc.builtin_dir_variables); } } } obj filebase = pc.name; if (akw[kw_filebase].set) { filebase = akw[kw_filebase].val; } SBUF(path); path_join(wk, &path, wk->muon_private, get_cstr(wk, filebase)); sbuf_pushs(wk, &path, ".pc"); if (!module_pkgconf_write(wk, path.buf, &pc)) { return false; } if (mainlib) { get_obj_build_target(wk, mainlib)->generated_pc = filebase; } make_obj(wk, res, obj_file); *get_obj_file(wk, *res) = sbuf_into_str(wk, &path); { SBUF(install_dir_buf); const char *install_dir; if (akw[kw_install_dir].set) { install_dir = get_cstr(wk, akw[kw_install_dir].val); } else { obj install_base; if (pc.dataonly) { get_option_value(wk, current_project(wk), "datadir", &install_base); } else { get_option_value(wk, current_project(wk), "libdir", &install_base); } path_join(wk, &install_dir_buf, get_cstr(wk, install_base), "pkgconfig"); install_dir = install_dir_buf.buf; } SBUF(dest); path_join(wk, &dest, install_dir, get_cstr(wk, filebase)); sbuf_pushs(wk, &dest, ".pc"); push_install_target(wk, *get_obj_file(wk, *res), sbuf_into_str(wk, &dest), 0); } return true; } const struct func_impl_name impl_tbl_module_pkgconfig[] = { { "generate", func_module_pkgconfig_generate, tc_file }, { NULL, NULL }, }; muon-0.2.0/src/functions/modules/python.c000066400000000000000000000022451441402162300204230ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/modules/python.h" #include "lang/interpreter.h" #include "platform/filesystem.h" static bool func_module_python_find_python(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } const char *cmd = "python3"; if (ao[0].set) { cmd = get_cstr(wk, ao[0].val); } SBUF(cmd_path); if (!fs_find_cmd(wk, &cmd_path, cmd)) { interp_error(wk, args_node, "python3 not found"); return false; } make_obj(wk, res, obj_external_program); struct obj_external_program *ep = get_obj_external_program(wk, *res); ep->found = true; ep->full_path = make_str(wk, cmd_path.buf); return true; } const struct func_impl_name impl_tbl_module_python[] = { { "find_installation", func_module_python_find_python, tc_external_program }, { NULL, NULL }, }; const struct func_impl_name impl_tbl_module_python3[] = { { "find_python", func_module_python_find_python, tc_external_program }, { NULL, NULL }, }; muon-0.2.0/src/functions/modules/sourceset.c000066400000000000000000000012201441402162300211060ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/modules/sourceset.h" static bool func_module_sourceset_source_set(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_source_set); struct obj_source_set *ss = get_obj_source_set(wk, *res); make_obj(wk, &ss->rules, obj_array); return true; } const struct func_impl_name impl_tbl_module_sourceset[] = { { "source_set", func_module_sourceset_source_set, tc_source_set, }, { NULL, NULL }, }; muon-0.2.0/src/functions/number.c000066400000000000000000000024011441402162300167140ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "functions/common.h" #include "functions/number.h" #include "lang/interpreter.h" #include "log.h" static bool func_number_is_odd(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (get_obj_number(wk, rcvr) & 1) != 0); return true; } static bool func_number_is_even(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, (get_obj_number(wk, rcvr) & 1) == 0); return true; } static bool func_number_to_string(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = make_strf(wk, "%" PRId64, get_obj_number(wk, rcvr)); return true; } const struct func_impl_name impl_tbl_number[] = { { "to_string", func_number_to_string, tc_string }, { "is_even", func_number_is_even, tc_bool }, { "is_odd", func_number_is_odd, tc_bool }, { NULL, NULL }, }; muon-0.2.0/src/functions/run_result.c000066400000000000000000000045261441402162300176400ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/run_result.h" #include "lang/interpreter.h" #include "log.h" static bool ensure_valid_run_result(struct workspace *wk, uint32_t node, obj rcvr) { struct obj_run_result *rr = get_obj_run_result(wk, rcvr); if ((rr->flags & run_result_flag_from_compile) && !(rr->flags & run_result_flag_compile_ok)) { interp_error(wk, node, "this run_result was not run because its source could not be compiled"); return false; } return true; } static bool func_run_result_returncode(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, args_node, rcvr)) { return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, get_obj_run_result(wk, rcvr)->status); return true; } static bool func_run_result_stdout(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, args_node, rcvr)) { return false; } *res = get_obj_run_result(wk, rcvr)->out; return true; } static bool func_run_result_stderr(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } if (!ensure_valid_run_result(wk, args_node, rcvr)) { return false; } *res = get_obj_run_result(wk, rcvr)->err; return true; } static bool func_run_result_compiled(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } struct obj_run_result *rr = get_obj_run_result(wk, rcvr); if (!(rr->flags & run_result_flag_from_compile)) { interp_error(wk, args_node, "this run_result is not from a compiler.run() call"); return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, rr->flags & run_result_flag_compile_ok); return true; } const struct func_impl_name impl_tbl_run_result[] = { { "compiled", func_run_result_compiled, tc_bool }, { "returncode", func_run_result_returncode, tc_number }, { "stderr", func_run_result_stderr, tc_string }, { "stdout", func_run_result_stdout, tc_string }, { NULL, NULL }, }; muon-0.2.0/src/functions/source_configuration.c000066400000000000000000000017571441402162300216700ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/source_configuration.h" #include "lang/interpreter.h" #include "log.h" static bool func_source_configuration_sources(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_source_configuration(wk, rcvr)->sources; return true; } static bool func_source_configuration_dependencies(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = get_obj_source_configuration(wk, rcvr)->dependencies; return true; } const struct func_impl_name impl_tbl_source_configuration[] = { { "sources", func_source_configuration_sources, tc_array, true }, { "dependencies", func_source_configuration_dependencies, tc_array, true }, { NULL, NULL }, }; muon-0.2.0/src/functions/source_set.c000066400000000000000000000212751441402162300176110ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "error.h" #include "functions/common.h" #include "functions/source_set.h" #include "lang/interpreter.h" #include "log.h" static enum iteration_result source_set_freeze_nested_iter(struct workspace *wk, void *_ctx, obj v) { if (get_obj_type(wk, v) == obj_source_set) { get_obj_source_set(wk, v)->frozen = true; } return ir_cont; } static bool source_set_add_rule(struct workspace *wk, obj rcvr, struct args_norm *posargs, struct args_kw *kw_when, struct args_kw *kw_if_true, struct args_kw *kw_if_false) { obj when = 0, if_true, if_false = 0; if (get_obj_array(wk, posargs->val)->len) { if (kw_when->set || kw_if_true->set || (kw_if_false && kw_if_false->set)) { interp_error(wk, posargs->node, "posargs not allowed when kwargs are used"); return false; } if_true = posargs->val; } else { when = kw_when->val; if_true = kw_if_true->val; if (kw_if_false) { if_false = kw_if_false->val; } } if (if_true) { obj_array_foreach(wk, if_true, NULL, source_set_freeze_nested_iter); } obj rule; make_obj(wk, &rule, obj_array); obj_array_push(wk, rule, when); obj_array_push(wk, rule, if_true); obj_array_push(wk, rule, if_false); obj_array_push(wk, get_obj_source_set(wk, rcvr)->rules, rule); return true; } static bool source_set_check_not_frozen(struct workspace *wk, uint32_t err_node, obj rcvr) { if (get_obj_source_set(wk, rcvr)->frozen) { interp_error(wk, err_node, "cannot modify frozen source set"); return false; } return true; } static bool func_source_set_add(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { const type_tag tc_ss_sources = tc_string | tc_file | tc_custom_target | tc_generated_list; struct args_norm an[] = { { ARG_TYPE_GLOB | tc_ss_sources | tc_dependency }, ARG_TYPE_NULL }; enum kwargs { kw_when, kw_if_true, kw_if_false, }; struct args_kw akw[] = { [kw_when] = { "when", ARG_TYPE_ARRAY_OF | tc_string | tc_dependency }, [kw_if_true] = { "if_true", ARG_TYPE_ARRAY_OF | tc_ss_sources | tc_dependency }, [kw_if_false] = { "if_false", ARG_TYPE_ARRAY_OF | tc_ss_sources }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!source_set_check_not_frozen(wk, args_node, rcvr)) { return false; } return source_set_add_rule(wk, rcvr, &an[0], &akw[kw_when], &akw[kw_if_true], &akw[kw_if_false]); } static bool func_source_set_add_all(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_source_set }, ARG_TYPE_NULL }; enum kwargs { kw_when, kw_if_true, }; struct args_kw akw[] = { [kw_when] = { "when", ARG_TYPE_ARRAY_OF | tc_string | tc_dependency }, [kw_if_true] = { "if_true", ARG_TYPE_ARRAY_OF | tc_source_set }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } if (!source_set_check_not_frozen(wk, args_node, rcvr)) { return false; } return source_set_add_rule(wk, rcvr, &an[0], &akw[kw_when], &akw[kw_if_true], NULL); } enum source_set_collect_mode { source_set_collect_sources, source_set_collect_dependencies, }; struct source_set_collect_ctx { enum source_set_collect_mode mode; bool strict; obj conf; obj res; // for rule_match_iter uint32_t err_node; bool match; }; static enum iteration_result source_set_collect_rules_iter(struct workspace *wk, void *_ctx, obj v); static enum iteration_result source_set_collect_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; switch (get_obj_type(wk, v)) { case obj_string: case obj_file: case obj_custom_target: case obj_generated_list: if (ctx->mode == source_set_collect_sources) { obj_array_push(wk, ctx->res, v); } break; case obj_dependency: if (ctx->mode == source_set_collect_dependencies) { obj_array_push(wk, ctx->res, v); } break; case obj_source_set: if (!obj_array_foreach(wk, get_obj_source_set(wk, v)->rules, ctx, source_set_collect_rules_iter)) { return ir_err; } break; default: UNREACHABLE; } return ir_cont; } static enum iteration_result source_set_rule_match_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; enum obj_type t = get_obj_type(wk, v); if (!ctx->conf && t != obj_dependency) { return ir_cont; } switch (t) { case obj_dependency: if (!(get_obj_dependency(wk, v)->flags & dep_flag_found)) { ctx->match = false; return ir_done; } break; case obj_string: { obj idx; if (!obj_dict_index(wk, ctx->conf, v, &idx)) { if (ctx->strict) { interp_error(wk, ctx->err_node, "key %o not in configuration", v); return ir_err; } ctx->match = false; return ir_done; } bool bv = false; switch (get_obj_type(wk, idx)) { case obj_bool: bv = get_obj_bool(wk, idx); break; case obj_string: bv = get_str(wk, idx)->len > 0; break; case obj_number: bv = get_obj_number(wk, idx) > 0; break; default: UNREACHABLE; } if (!bv) { ctx->match = false; return ir_done; } break; } default: UNREACHABLE; } return ir_cont; } static enum iteration_result source_set_collect_when_deps_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; if (get_obj_type(wk, v) == obj_dependency) { obj_array_push(wk, ctx->res, v); } return ir_cont; } static enum iteration_result source_set_collect_rules_iter(struct workspace *wk, void *_ctx, obj v) { struct source_set_collect_ctx *ctx = _ctx; obj when, if_true, if_false; obj_array_index(wk, v, 0, &when); obj_array_index(wk, v, 1, &if_true); obj_array_index(wk, v, 2, &if_false); ctx->match = true; if (when && !obj_array_foreach_flat(wk, when, ctx, source_set_rule_match_iter)) { return ir_err; } if (ctx->match && if_true) { if (when && ctx->mode == source_set_collect_dependencies) { obj_array_foreach_flat(wk, when, ctx, source_set_collect_when_deps_iter); } obj_array_foreach_flat(wk, if_true, ctx, source_set_collect_iter); } if ((!ctx->conf || !ctx->match) && if_false) { obj_array_foreach_flat(wk, if_false, ctx, source_set_collect_iter); } return ir_cont; } static bool source_set_collect(struct workspace *wk, uint32_t err_node, obj rcvr, obj conf, enum source_set_collect_mode mode, bool strict, obj *res) { obj arr; make_obj(wk, &arr, obj_array); struct source_set_collect_ctx ctx = { .mode = mode, .conf = conf, .strict = strict, .res = arr, }; struct obj_source_set *ss = get_obj_source_set(wk, rcvr); if (!obj_array_foreach(wk, ss->rules, &ctx, source_set_collect_rules_iter)) { return false; } obj_array_dedup(wk, arr, res); return true; } static bool func_source_set_all_sources(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } return source_set_collect(wk, 0, rcvr, 0, source_set_collect_sources, true, res); } static bool func_source_set_all_dependencies(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } return source_set_collect(wk, 0, rcvr, 0, source_set_collect_dependencies, true, res); } static bool func_source_set_apply(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { tc_configuration_data | tc_dict }, ARG_TYPE_NULL }; enum kwargs { kw_strict, }; struct args_kw akw[] = { [kw_strict] = { "strict", tc_bool }, 0 }; if (!interp_args(wk, args_node, an, NULL, akw)) { return false; } struct obj_source_set *ss = get_obj_source_set(wk, rcvr); ss->frozen = true; obj dict = 0; switch (get_obj_type(wk, an[0].val)) { case obj_dict: dict = an[0].val; break; case obj_configuration_data: dict = get_obj_configuration_data(wk, an[0].val)->dict; break; default: UNREACHABLE; } bool strict = akw[kw_strict].set ? get_obj_bool(wk, akw[kw_strict].val) : true; make_obj(wk, res, obj_source_configuration); struct obj_source_configuration *sc = get_obj_source_configuration(wk, *res); if (!source_set_collect(wk, akw[kw_strict].node, rcvr, dict, source_set_collect_sources, strict, &sc->sources)) { return false; } if (!source_set_collect(wk, akw[kw_strict].node, rcvr, dict, source_set_collect_dependencies, strict, &sc->dependencies)) { return false; } return true; } const struct func_impl_name impl_tbl_source_set[] = { { "add", func_source_set_add, 0, true }, { "add_all", func_source_set_add_all, 0, true }, { "all_sources", func_source_set_all_sources, tc_array, true }, { "all_dependencies", func_source_set_all_dependencies, tc_array, true }, { "apply", func_source_set_apply, tc_source_configuration, true }, { NULL, NULL }, }; muon-0.2.0/src/functions/string.c000066400000000000000000000267111441402162300167440ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Harley Swick * SPDX-FileCopyrightText: Eli Schwartz * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "buf_size.h" #include "coerce.h" #include "functions/common.h" #include "functions/string.h" #include "guess.h" #include "lang/interpreter.h" #include "log.h" #include "rpmvercmp.h" static bool func_strip(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } *res = str_strip(wk, get_str(wk, rcvr), ao[0].set ? get_str(wk, ao[0].val) : NULL); return true; } static bool func_to_upper(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = str_clone(wk, wk, rcvr); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if ('a' <= ss->s[i] && ss->s[i] <= 'z') { ((char *)ss->s)[i] -= 32; } } return true; } static bool func_to_lower(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = str_clone(wk, wk, rcvr); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if ('A' <= ss->s[i] && ss->s[i] <= 'Z') { ((char *)ss->s)[i] += 32; } } return true; } bool string_format(struct workspace *wk, uint32_t err_node, obj str, obj *res, void *ctx, string_format_cb cb) { struct str key; const struct str *ss_in = get_str(wk, str); uint32_t i, id_start = 0, id_end = 0; bool reading_id = false; *res = make_str(wk, ""); for (i = 0; i < ss_in->len; ++i) { if (ss_in->s[i] == '@') { if (reading_id) { obj elem; id_end = i + 1; if (i == id_start) { str_app(wk, *res, "@"); id_start = i + 1; reading_id = true; key.len = 0; continue; } key = (struct str){ .s = &ss_in->s[id_start], .len = i - id_start }; switch (cb(wk, err_node, ctx, &key, &elem)) { case format_cb_not_found: { interp_error(wk, err_node, "key '%.*s' not found", key.len, key.s); return false; } case format_cb_error: return false; case format_cb_found: { obj coerced; if (!coerce_string(wk, err_node, elem, &coerced)) { return false; } const struct str *ss = get_str(wk, coerced); str_appn(wk, *res, ss->s, ss->len); break; } case format_cb_skip: { str_app(wk, *res, "@"); i = id_start - 1; id_end = id_start; id_start = 0; reading_id = false; continue; } } reading_id = false; } else { if (i) { str_appn(wk, *res, &ss_in->s[id_end], i - id_end); } id_start = i + 1; reading_id = true; key.len = 0; } } } if (reading_id) { str_app(wk, *res, "@"); str_appn(wk, *res, &ss_in->s[id_start], i - id_start); } else { if (i > id_end) { str_appn(wk, *res, &ss_in->s[id_end], i - id_end); } } return true; } struct func_format_ctx { obj arr; }; static enum format_cb_result func_format_cb(struct workspace *wk, uint32_t node, void *_ctx, const struct str *key, uint32_t *elem) { struct func_format_ctx *ctx = _ctx; int64_t i; if (!str_to_i(key, &i)) { return format_cb_skip; } if (!boundscheck(wk, node, get_obj_array(wk, ctx->arr)->len, &i)) { return format_cb_error; } obj_array_index(wk, ctx->arr, i, elem); return format_cb_found; } static bool func_format(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | tc_number | tc_bool | tc_string | tc_file }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } struct func_format_ctx ctx = { .arr = an[0].val, }; obj str; if (!string_format(wk, an[0].node, rcvr, &str, &ctx, func_format_cb)) { return false; } *res = str; return true; } static bool func_underscorify(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } *res = str_clone(wk, wk, rcvr); const struct str *ss = get_str(wk, *res); uint32_t i; for (i = 0; i < ss->len; ++i) { if (!(('a' <= ss->s[i] && ss->s[i] <= 'z') || ('A' <= ss->s[i] && ss->s[i] <= 'Z') || ('0' <= ss->s[i] && ss->s[i] <= '9'))) { ((char *)ss->s)[i] = '_'; } } return true; } static bool func_split(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } const struct str *split = ao[0].set ? get_str(wk, ao[0].val) : NULL, *ss = get_str(wk, rcvr); *res = str_split(wk, ss, split); return true; } static bool func_join(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { ARG_TYPE_GLOB | obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } return obj_array_join(wk, true, an[0].val, rcvr, res); } struct version_compare_ctx { bool res; uint32_t err_node; const struct str *ver1; }; static enum iteration_result version_compare_iter(struct workspace *wk, void *_ctx, obj s2) { struct version_compare_ctx *ctx = _ctx; struct str ver2 = *get_str(wk, s2); enum op_type { op_ge, op_gt, op_eq, op_ne, op_le, op_lt, }; enum op_type op = op_eq; struct { const struct str name; enum op_type op; } ops[] = { { WKSTR(">="), op_ge, }, { WKSTR(">"), op_gt, }, { WKSTR("=="), op_eq, }, { WKSTR("!="), op_ne, }, { WKSTR("<="), op_le, }, { WKSTR("<"), op_lt, }, { WKSTR("="), op_eq, }, }; uint32_t i; for (i = 0; i < ARRAY_LEN(ops); ++i) { if (str_startswith(&ver2, &ops[i].name)) { op = ops[i].op; ver2.s += ops[i].name.len; ver2.len -= ops[i].name.len; break; } } int8_t cmp = rpmvercmp(ctx->ver1, &ver2); switch (op) { case op_eq: ctx->res = cmp == 0; break; case op_ne: ctx->res = cmp != 0; break; case op_gt: ctx->res = cmp == 1; break; case op_ge: ctx->res = cmp >= 0; break; case op_lt: ctx->res = cmp == -1; break; case op_le: ctx->res = cmp <= 0; break; } if (!ctx->res) { return ir_done; } return ir_cont; } bool version_compare(struct workspace *wk, uint32_t err_node, const struct str *ver, obj cmp_arr, bool *res) { struct version_compare_ctx ctx = { .err_node = err_node, .ver1 = ver, }; if (!obj_array_foreach(wk, cmp_arr, &ctx, version_compare_iter)) { return false; } *res = ctx.res; return true; } static bool func_version_compare(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } struct version_compare_ctx ctx = { .err_node = an[0].node, .ver1 = get_str(wk, rcvr), }; if (version_compare_iter(wk, &ctx, an[0].val) == ir_err) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, ctx.res); return true; } static bool func_string_to_int(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } const struct str *ss = get_str(wk, rcvr); int64_t n; if (!str_to_i(ss, &n)) { interp_error(wk, args_node, "unable to parse %o", rcvr); return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, n); return true; } static bool func_string_startswith(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, str_startswith(get_str(wk, rcvr), get_str(wk, an[0].val))); return true; } static bool func_string_endswith(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, str_endswith(get_str(wk, rcvr), get_str(wk, an[0].val))); return true; } static bool func_string_substring(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm ao[] = { { obj_number }, { obj_number }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, NULL, ao, NULL)) { return false; } const struct str *s = get_str(wk, rcvr); int64_t start = 0, end = s->len; if (ao[0].set) { start = get_obj_number(wk, ao[0].val); } if (ao[1].set) { end = get_obj_number(wk, ao[1].val); } if (start < 0) { start = s->len + start; } if (end < 0) { end = s->len + end; } if (start > s->len || start < 0) { assert(ao[0].set); interp_error(wk, ao[0].node, "substring start out of bounds"); return false; } else if (end > s->len || end < 0) { assert(ao[1].set); interp_error(wk, ao[1].node, "substring end out of bounds"); return false; } else if (end < start) { assert(ao[1].set); interp_error(wk, ao[1].node, "substring end before start"); return false; } *res = make_strn(wk, &s->s[start], end - start); return true; } static bool func_string_replace(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const struct str *s = get_str(wk, rcvr); const struct str *find = get_str(wk, an[0].val); const struct str *replace = get_str(wk, an[1].val); struct str tmp, pre = { .s = s->s, .len = 0, }; *res = make_str(wk, ""); uint32_t i; for (i = 0; i < s->len; ++i) { tmp = (struct str) { .s = &s->s[i], .len = s->len - i, }; if (str_startswith(&tmp, find)) { str_appn(wk, *res, pre.s, pre.len); str_appn(wk, *res, replace->s, replace->len); i += find->len; pre.s = &s->s[i]; pre.len = 0; --i; } else { ++pre.len; } } str_appn(wk, *res, pre.s, pre.len); return true; } static bool func_string_contains(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, NULL, NULL)) { return false; } const struct str *s = get_str(wk, rcvr); const struct str *find = get_str(wk, an[0].val); struct str tmp; bool found = false; uint32_t i; for (i = 0; i < s->len; ++i) { tmp = (struct str) { .s = &s->s[i], .len = s->len - i, }; if (str_startswith(&tmp, find)) { found = true; break; } } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, found); return true; } const struct func_impl_name impl_tbl_string[] = { { "contains", func_string_contains, tc_bool, true }, { "endswith", func_string_endswith, tc_bool, true }, { "format", func_format, tc_string, true }, { "join", func_join, tc_string, true }, { "replace", func_string_replace, tc_string, true }, { "split", func_split, tc_array, true }, { "startswith", func_string_startswith, tc_bool, true }, { "strip", func_strip, tc_string, true }, { "substring", func_string_substring, tc_string, true }, { "to_int", func_string_to_int, tc_number, true }, { "to_lower", func_to_lower, tc_string, true }, { "to_upper", func_to_upper, tc_string, true }, { "underscorify", func_underscorify, tc_string, true }, { "version_compare", func_version_compare, tc_bool, true }, { NULL, NULL }, }; muon-0.2.0/src/functions/subproject.c000066400000000000000000000032031441402162300176050ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/subproject.h" #include "lang/interpreter.h" #include "log.h" bool subproject_get_variable(struct workspace *wk, uint32_t node, obj name_id, obj fallback, obj subproj, obj *res) { const char *name = get_cstr(wk, name_id); struct obj_subproject *sub = get_obj_subproject(wk, subproj); if (!sub->found) { interp_error(wk, node, "subproject was not found"); return false; } if (!wk->get_variable(wk, name, res, sub->id)) { if (!fallback) { return false; } else { *res = fallback; } } return true; } static bool func_subproject_get_variable(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { struct args_norm an[] = { { obj_string }, ARG_TYPE_NULL }; struct args_norm ao[] = { { tc_any }, ARG_TYPE_NULL }; if (!interp_args(wk, args_node, an, ao, NULL)) { return false; } if (!subproject_get_variable(wk, an[0].node, an[0].val, ao[0].val, rcvr, res)) { interp_error(wk, an[0].node, "subproject does not define '%s'", get_cstr(wk, an[0].val)); return false; } return true; } static bool func_subproject_found(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { if (!interp_args(wk, args_node, NULL, NULL, NULL)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, get_obj_subproject(wk, rcvr)->found); return true; } const struct func_impl_name impl_tbl_subproject[] = { { "found", func_subproject_found, tc_bool }, { "get_variable", func_subproject_get_variable, tc_any }, { NULL, NULL }, }; muon-0.2.0/src/functions/template.c000066400000000000000000000006241441402162300172440ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include "functions/common.h" #include "functions/xxx.h" #include "lang/interpreter.h" #include "log.h" static bool func_(struct workspace *wk, obj rcvr, uint32_t args_node, obj *res) { } const struct func_impl_name impl_tbl_xxx[] = { { "", func_ }, { NULL, NULL }, }; muon-0.2.0/src/guess.c000066400000000000000000000014001441402162300145400ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include "guess.h" #include "log.h" bool guess_version(struct workspace *wk, const char *src, obj *res) { uint32_t dots = 0, ver_len = 0, new_len, new_dots; const char *p, *ver = NULL; for (p = src; *p; ++p) { new_len = 0; new_dots = 0; while (('0' <= p[new_len] && p[new_len] <= '9') || p[new_len] == '.') { if (p[new_len] == '.') { ++new_dots; } ++new_len; } if (new_dots > dots) { ver = p; dots = new_dots; ver_len = new_len; } if (new_len) { p += new_len - 1; } } if (!ver) { return false; } obj s = make_strn(wk, ver, ver_len); *res = s; return true; } muon-0.2.0/src/install.c000066400000000000000000000126721441402162300150750ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "coerce.h" #include "error.h" #include "install.h" #include "lang/interpreter.h" #include "log.h" #include "options.h" #include "platform/path.h" static bool rwx_to_perm(const char *rwx, uint32_t *perm) { assert(rwx && perm); if (strlen(rwx) != 9) { return false; } uint32_t bit = S_IRUSR; // 400 uint32_t i; for (i = 0; i < 9; ++i) { switch (rwx[i]) { case '-': break; case 't': case 'T': if (i != 8) { return false; } if (!S_ISVTX) { LOG_W("sticky bit requested, but support is not compiled in"); } *perm |= S_ISVTX; break; case 's': case 'S': if (i != 2 && i != 5) { return false; } *perm |= i == 2 ? S_ISUID : S_ISGID; break; case 'r': if (i != 0 && i != 3 && i != 6) { return false; } break; case 'w': if (i != 1 && i != 4 && i != 7) { return false; } break; case 'x': if (i != 2 && i != 5 && i != 8) { return false; } break; default: return false; } if (rwx[i] != '-' && rwx[i] != 'S' && rwx[i] != 'T') { *perm |= bit; } bit >>= 1; // 400 200 100 40 20 10 4 2 1 } // printf("%o\n", *perm); return true; } struct obj_install_target * push_install_target(struct workspace *wk, obj src, obj dest, obj mode) { obj id; make_obj(wk, &id, obj_install_target); struct obj_install_target *tgt = get_obj_install_target(wk, id); tgt->src = src; tgt->has_perm = false; if (mode) { uint32_t len = get_obj_array(wk, mode)->len; if (len > 3) { LOG_E("install_mode must have 3 or less elements"); return NULL; } if (len > 1) { LOG_W("TODO: install user/group mode"); } obj perm; obj_array_index(wk, mode, 0, &perm); switch (get_obj_type(wk, perm)) { case obj_bool: tgt->has_perm = false; break; case obj_string: if (!rwx_to_perm(get_cstr(wk, perm), &tgt->perm)) { LOG_E("install_mode has malformed permission string: %s", get_cstr(wk, perm)); return NULL; } tgt->has_perm = true; break; default: return NULL; } } obj sdest; if (path_is_absolute(get_cstr(wk, dest))) { sdest = dest; } else { obj prefix; get_option_value(wk, current_project(wk), "prefix", &prefix); SBUF(buf); path_join(wk, &buf, get_cstr(wk, prefix), get_cstr(wk, dest)); sdest = sbuf_into_str(wk, &buf); } tgt->dest = sdest; tgt->type = install_target_default; obj_array_push(wk, wk->install, id); return tgt; } bool push_install_target_install_dir(struct workspace *wk, obj src, obj install_dir, obj mode) { SBUF(basename); path_basename(wk, &basename, get_cstr(wk, src)); SBUF(dest); path_join(wk, &dest, get_cstr(wk, install_dir), basename.buf); obj sdest = sbuf_into_str(wk, &dest); return !!push_install_target(wk, src, sdest, mode); } struct push_install_targets_ctx { obj install_dirs; obj install_mode; bool install_dirs_is_arr, preserve_path; uint32_t i, err_node; }; static enum iteration_result push_install_targets_iter(struct workspace *wk, void *_ctx, obj val_id) { struct push_install_targets_ctx *ctx = _ctx; obj install_dir; if (ctx->install_dirs_is_arr) { obj_array_index(wk, ctx->install_dirs, ctx->i, &install_dir); assert(install_dir); } else { install_dir = ctx->install_dirs; } ++ctx->i; enum obj_type dt = get_obj_type(wk, install_dir); if (dt == obj_bool && !get_obj_bool(wk, install_dir)) { // skip if we get passed `false` for an install dir return ir_cont; } else if (dt != obj_string) { interp_error(wk, ctx->err_node, "install_dir values must be strings, got %s", obj_type_to_s(dt)); return ir_err; } obj src, dest, f; switch (get_obj_type(wk, val_id)) { case obj_string: { if (!coerce_file(wk, ctx->err_node, val_id, &f)) { return ir_err; } if (!ctx->preserve_path) { goto handle_file; } SBUF(dest_path); path_join(wk, &dest_path, get_cstr(wk, install_dir), get_cstr(wk, val_id)); src = *get_obj_file(wk, f); dest = sbuf_into_str(wk, &dest_path); break; } case obj_file: if (ctx->preserve_path) { interp_error(wk, ctx->err_node, "file arguments are ambiguous with preserve_path: true"); return ir_err; } f = val_id; handle_file: { SBUF(basename); path_basename(wk, &basename, get_file_path(wk, f)); SBUF(dest_path); path_join(wk, &dest_path, get_cstr(wk, install_dir), basename.buf); src = *get_obj_file(wk, f); dest = sbuf_into_str(wk, &dest_path); } break; default: UNREACHABLE; } if (!push_install_target(wk, src, dest, ctx->install_mode)) { return ir_err; } return ir_cont; } bool push_install_targets(struct workspace *wk, uint32_t err_node, obj filenames, obj install_dirs, obj install_mode, bool preserve_path) { struct push_install_targets_ctx ctx = { .err_node = err_node, .preserve_path = preserve_path, .install_dirs = install_dirs, .install_mode = install_mode, .install_dirs_is_arr = get_obj_type(wk, install_dirs) == obj_array, }; assert(ctx.install_dirs_is_arr || get_obj_type(wk, install_dirs) == obj_string); if (ctx.install_dirs_is_arr) { struct obj_array *a1 = get_obj_array(wk, filenames); struct obj_array *a2 = get_obj_array(wk, install_dirs); if (a1->len != a2->len) { interp_error(wk, err_node, "number of install_dirs does not match number of sources"); return false; } } return obj_array_foreach(wk, filenames, &ctx, push_install_targets_iter); } muon-0.2.0/src/lang/000077500000000000000000000000001441402162300141745ustar00rootroot00000000000000muon-0.2.0/src/lang/analyze.c000066400000000000000000001135431441402162300160120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include "buf_size.h" #include "coerce.h" #include "data/hash.h" #include "error.h" #include "functions/common.h" #include "functions/modules.h" #include "lang/analyze.h" #include "lang/eval.h" #include "lang/interpreter.h" #include "lang/parser.h" #include "lang/workspace.h" #include "log.h" #include "platform/path.h" static bool analyze_error; static const struct analyze_opts *analyze_opts; struct analyze_file_entrypoint { bool is_root, has_diagnostic; enum log_level lvl; uint32_t line, col, src_idx; }; static struct darr analyze_entrypoint_stack, analyze_entrypoint_stacks; struct assignment { const char *name; obj o; bool accessed, default_var; uint32_t line, col, src_idx; uint32_t ep_stacks_i; uint32_t ep_stack_len; }; struct scope_group { struct darr scopes; }; struct { struct darr groups; struct darr base; } assignment_scopes; static void copy_analyze_entrypoint_stack(uint32_t *ep_stacks_i, uint32_t *ep_stack_len) { if (analyze_entrypoint_stack.len) { *ep_stacks_i = analyze_entrypoint_stacks.len; darr_grow_by(&analyze_entrypoint_stacks, analyze_entrypoint_stack.len); *ep_stack_len = analyze_entrypoint_stack.len; memcpy(darr_get(&analyze_entrypoint_stacks, *ep_stacks_i), analyze_entrypoint_stack.e, sizeof(struct analyze_file_entrypoint) * analyze_entrypoint_stack.len); } else { *ep_stacks_i = 0; *ep_stack_len = 0; } } static void mark_analyze_entrypoint_as_containing_diagnostic(uint32_t ep_stacks_i, enum log_level lvl) { struct analyze_file_entrypoint *ep = darr_get(&analyze_entrypoint_stacks, ep_stacks_i); ep->has_diagnostic = true; ep->lvl = lvl; } static bool analyze_diagnostic_enabled(enum analyze_diagnostic d) { return analyze_opts->enabled_diagnostics & d; } static const char * inspect_typeinfo(struct workspace *wk, obj t) { if (get_obj_type(wk, t) == obj_typeinfo) { struct obj_typeinfo *ti = get_obj_typeinfo(wk, t); return typechecking_type_to_s(wk, ti->type); } else { return obj_type_to_s(get_obj_type(wk, t)); } } static obj make_typeinfo(struct workspace *wk, type_tag t, type_tag sub_t) { assert(t & obj_typechecking_type_tag); if (sub_t) { assert(sub_t & obj_typechecking_type_tag); } obj res; make_obj(wk, &res, obj_typeinfo); struct obj_typeinfo *type = get_obj_typeinfo(wk, res); type->type = t; type->subtype = sub_t; return res; } static type_tag coerce_type_tag(struct workspace *wk, obj r) { type_tag t = get_obj_type(wk, r); if (t == obj_typeinfo) { return get_obj_typeinfo(wk, r)->type; } else { return obj_type_to_tc_type(t); } } static void merge_types(struct workspace *wk, struct obj_typeinfo *a, obj r) { a->type |= coerce_type_tag(wk, r); } struct analyze_ctx { type_tag expected; type_tag found; const struct func_impl_name *found_func; struct obj_typeinfo ti; enum comparison_type comparison_type; obj l; }; typedef void ((analyze_for_each_type_cb)(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, type_tag t, obj *res)); static void analyze_for_each_type(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, obj o, type_tag typemask, analyze_for_each_type_cb cb, obj *res) { obj r = 0; type_tag t; if ((t = get_obj_type(wk, o)) == obj_typeinfo) { t = get_obj_typeinfo(wk, o)->type; } if (t & obj_typechecking_type_tag) { if (t == tc_disabler) { interp_warning(wk, n_id, "this expression is always disabled"); ctx->expected = tc_any; *res = make_typeinfo(wk, tc_disabler, 0); ctx->found = 2; // set found to > 1 to indicate the // method exists but it is unknown // which one it is. return; } else if ((t & tc_disabler) == tc_disabler) { t &= ~tc_disabler; t |= obj_typechecking_type_tag; } struct obj_typeinfo res_t = { 0 }; if (typemask) { t &= typemask; } type_tag ot; for (ot = 1; ot <= tc_type_count; ++ot) { type_tag tc = obj_type_to_tc_type(ot); if ((t & tc) == tc) { r = 0; cb(wk, ctx, n_id, ot, &r); if (r) { merge_types(wk, &res_t, r); } } } make_obj(wk, res, obj_typeinfo); *get_obj_typeinfo(wk, *res) = res_t; } else { cb(wk, ctx, n_id, t, res); } } /* * Variable assignment and retrieval is handled with the following functions. * The additional complexity is required due to variables that are * conditionally assigned, e.g. assigned in an if-block, or foreach loop. * * When a conditional block is started, a "scope group" is created, and then * every branch of the if statement gets its own sub-scope. At the end of the * if statement, all the sub scopes are merged (conflicting object types get * merged) into the parent scope, and the scope group is popped. */ static bool assign_lookup_scope_i(const char *name, struct darr *scope, uint32_t *res) { uint32_t i; for (i = 0; i < scope->len; ++i) { struct assignment *a = darr_get(scope, i); if (strcmp(a->name, name) == 0) { *res = i; return true; } } return false; } static struct assignment * assign_lookup_scope(const char *name, struct darr *scope) { uint32_t i; if (assign_lookup_scope_i(name, scope, &i)) { return darr_get(scope, i); } else { return NULL; } } static void analyze_unassign(struct workspace *wk, const char *name) { int32_t i; uint32_t idx = 0; struct darr *containing_scope = NULL; bool is_base = false; for (i = assignment_scopes.groups.len - 1; i >= 0; --i) { struct scope_group *g = darr_get(&assignment_scopes.groups, i); if (!(g->scopes.len)) { continue; } struct darr *scope = darr_get(&g->scopes, g->scopes.len - 1); if (assign_lookup_scope_i(name, scope, &idx)) { containing_scope = scope; break; } } obj _; if (!containing_scope && wk->projects.len && get_obj_id(wk, name, &_, wk->cur_project)) { if (!assign_lookup_scope_i(name, &assignment_scopes.base, &idx)) { UNREACHABLE; } containing_scope = &assignment_scopes.base; is_base = true; } if (!containing_scope) { // variable not found... return; } darr_del(containing_scope, idx); if (is_base) { hash_unset_str(¤t_project(wk)->scope, name); } } static struct assignment * assign_lookup(struct workspace *wk, const char *name) { int32_t i; uint32_t id; struct assignment *found = NULL; for (i = assignment_scopes.groups.len - 1; i >= 0; --i) { struct scope_group *g = darr_get(&assignment_scopes.groups, i); if (!(g->scopes.len)) { continue; } struct darr *scope = darr_get(&g->scopes, g->scopes.len - 1); if ((found = assign_lookup_scope(name, scope))) { break; } } if (!found && wk->projects.len && get_obj_id(wk, name, &id, wk->cur_project)) { found = darr_get(&assignment_scopes.base, id); } return found; } static void check_reassign_to_different_type(struct workspace *wk, struct assignment *a, obj new_val, struct assignment *new_a, uint32_t n_id) { if (!analyze_diagnostic_enabled(analyze_diagnostic_reassign_to_conflicting_type)) { return; } type_tag t1 = coerce_type_tag(wk, a->o), t2 = coerce_type_tag(wk, new_val); if ((t1 & t2) != t2) { char buf[BUF_SIZE_2k] = { 0 }; snprintf(buf, BUF_SIZE_2k, "reassignment of variable %s with type %s to conflicting type %s", a->name, typechecking_type_to_s(wk, t1), typechecking_type_to_s(wk, t2)); if (new_a) { error_diagnostic_store_push( new_a->src_idx, new_a->line, new_a->col, log_warn, buf ); } else { interp_warning(wk, n_id, "%s", buf); } } } static struct assignment * scope_assign(struct workspace *wk, const char *name, obj o, uint32_t n_id) { if (!o) { interp_error(wk, n_id, "assigning variable to null"); analyze_error = true; o = make_typeinfo(wk, tc_any, 0); } struct darr *s; if (assignment_scopes.groups.len) { struct scope_group *g = darr_get(&assignment_scopes.groups, assignment_scopes.groups.len - 1); assert(g->scopes.len); s = darr_get(&g->scopes, g->scopes.len - 1); } else { s = &assignment_scopes.base; } struct assignment *a; if (wk->impure_loop_depth && (a = assign_lookup(wk, name))) { /* L("%s reassigned / shadowed variable in impure loop", name); */ enum obj_type new_type = get_obj_type(wk, o); if (new_type != obj_typeinfo && !obj_equal(wk, a->o, o)) { o = make_typeinfo(wk, obj_type_to_tc_type(new_type), 0); } } if ((a = assign_lookup_scope(name, s))) { // re-assign check_reassign_to_different_type(wk, a, o, NULL, n_id); a->o = o; return a; } // builtin variables don't have a source location struct node *n = NULL; uint32_t src_idx = 0, ep_stack_len = 0, ep_stacks_i = 0; if (wk->src && n_id) { n = get_node(wk->ast, n_id); // push the source so that we have it later for error reporting src_idx = error_diagnostic_store_push_src(wk->src); copy_analyze_entrypoint_stack(&ep_stacks_i, &ep_stack_len); } darr_push(s, &(struct assignment) { .name = name, .o = o, .line = n ? n->line : 0, .col = n ? n->col : 0, .src_idx = src_idx, .ep_stacks_i = ep_stacks_i, .ep_stack_len = ep_stack_len, }); if (!assignment_scopes.groups.len) { struct hash *scope; if (wk->projects.len) { scope = ¤t_project(wk)->scope; } else { scope = &wk->scope; } hash_set_str(scope, name, s->len - 1); } return darr_get(s, s->len - 1); } static void push_scope_group_scope(void) { assert(assignment_scopes.groups.len); struct scope_group *g = darr_get(&assignment_scopes.groups, assignment_scopes.groups.len - 1); darr_push(&g->scopes, &(struct darr) { 0 }); struct darr *scope = darr_get(&g->scopes, g->scopes.len - 1); darr_init(scope, 256, sizeof(struct assignment)); } static void push_scope_group(void) { struct scope_group g = { 0 }; darr_init(&g.scopes, 4, sizeof(struct darr)); darr_push(&assignment_scopes.groups, &g); } static void merge_objects(struct workspace *wk, struct assignment *new, struct assignment *old) { type_tag new_type = get_obj_type(wk, new->o); type_tag old_type = get_obj_type(wk, old->o); if (new_type != obj_typeinfo) { new->o = make_typeinfo(wk, obj_type_to_tc_type(new_type), 0); } if (old_type != obj_typeinfo) { old->o = make_typeinfo(wk, obj_type_to_tc_type(old_type), 0); } check_reassign_to_different_type(wk, new, old->o, old, 0); merge_types(wk, get_obj_typeinfo(wk, new->o), old->o); assert(get_obj_type(wk, new->o) == obj_typeinfo); assert(get_obj_type(wk, old->o) == obj_typeinfo); } static void pop_scope_group(struct workspace *wk) { assert(assignment_scopes.groups.len); size_t idx = assignment_scopes.groups.len - 1; struct scope_group *g = darr_get(&assignment_scopes.groups, idx); darr_del(&assignment_scopes.groups, idx); if (!g->scopes.len) { // empty scope group return; } struct darr *base = darr_get(&g->scopes, 0); uint32_t i, j; for (i = 1; i < g->scopes.len; ++i) { struct darr *scope = darr_get(&g->scopes, i); for (j = 0; j < scope->len; ++j) { struct assignment *b, *a = darr_get(scope, j); if ((b = assign_lookup_scope(a->name, base))) { merge_objects(wk, b, a); } else { darr_push(base, a); } } } for (i = 0; i < base->len; ++i) { struct assignment *a = darr_get(base, i), *b; if ((b = assign_lookup(wk, a->name))) { merge_objects(wk, b, a); } else { type_tag type = get_obj_type(wk, a->o); if (type != obj_typeinfo) { a->o = make_typeinfo(wk, obj_type_to_tc_type(type), 0); } b = scope_assign(wk, a->name, a->o, 0); b->accessed = a->accessed; b->line = a->line; b->col = a->col; b->src_idx = a->src_idx; } } for (i = 0; i < g->scopes.len; ++i) { struct darr *scope = darr_get(&g->scopes, i); darr_destroy(scope); } darr_destroy(&g->scopes); } /* *----------------------------------------------------------------------------- */ static enum iteration_result is_dict_with_pure_keys_iter(struct workspace *wk, void *_ctx, obj k, obj v) { if (get_obj_type(wk, k) == obj_typeinfo) { return ir_err; } return ir_cont; } static bool is_dict_with_pure_keys(struct workspace *wk, obj o) { return obj_dict_foreach(wk, o, NULL, is_dict_with_pure_keys_iter); } static bool is_pure_arithmetic_object(struct workspace *wk, obj o) { switch (get_obj_type(wk, o)) { case obj_typeinfo: case obj_disabler: return false; case obj_dict: return is_dict_with_pure_keys(wk, o); default: return true; } } static void mark_node_visited(struct node *n) { n->chflg |= node_visited; } /* *----------------------------------------------------------------------------- */ static bool analyze_all_function_arguments(struct workspace *wk, uint32_t n_id, uint32_t args_node) { bool ret = true; struct node *args = get_node(wk->ast, args_node); bool had_kwargs = false; obj res; uint32_t val_node; while (args->type != node_empty) { mark_node_visited(args); if (args->subtype == arg_kwarg) { had_kwargs = true; val_node = args->r; mark_node_visited(get_node(wk->ast, args->l)); } else { if (had_kwargs) { interp_error(wk, args_node, "non-kwarg after kwargs"); ret = false; } val_node = args->l; } if (!wk->interp_node(wk, val_node, &res)) { ret = false; } if (args->chflg & node_child_c) { args_node = args->c; args = get_node(wk->ast, args_node); } else { break; } } return ret; } static bool analyze_function_call(struct workspace *wk, uint32_t n_id, uint32_t args_node, const struct func_impl_name *fi, obj rcvr, obj *res) { bool ret = true; obj func_res; bool old_analyze_error = analyze_error; analyze_error = false; bool subdir_func = !rcvr && strcmp(fi->name, "subdir") == 0; analyze_all_function_arguments(wk, n_id, args_node); if (subdir_func) { struct node *n = get_node(wk->ast, n_id); darr_push(&analyze_entrypoint_stack, &(struct analyze_file_entrypoint) { .src_idx = error_diagnostic_store_push_src(wk->src), .line = n->line, .col = n->col, .is_root = analyze_entrypoint_stack.len == 0, }); } bool was_pure; if (!analyze_function(wk, fi, args_node, rcvr, &func_res, &was_pure) || analyze_error) { if (subdir_func && analyze_opts->subdir_error) { interp_error(wk, n_id, "in subdir"); } ret = false; } if (subdir_func) { if (!was_pure) { interp_warning(wk, n_id, "unable to analyze subdir call"); } darr_del(&analyze_entrypoint_stack, analyze_entrypoint_stack.len - 1); } analyze_error = old_analyze_error; if (func_res) { *res = func_res; } else if (fi->return_type) { *res = make_typeinfo(wk, fi->return_type, 0); } return ret; } static bool analyze_chained(struct workspace *wk, uint32_t n_id, obj l_id, obj *res); static void analyze_method(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, type_tag rcvr_type, obj *res) { struct node *n = get_node(wk->ast, n_id); const char *name = get_node(wk->ast, n->r)->dat.s; mark_node_visited(get_node(wk->ast, n->r)); const struct func_impl_name *fi; if (rcvr_type == obj_module && get_obj_type(wk, ctx->l) == obj_module && get_obj_module(wk, ctx->l)->found) { struct obj_module *m = get_obj_module(wk, ctx->l); enum module mod = m->module; if (!(fi = module_func_lookup(wk, name, mod))) { return; } } else { const struct func_impl_name *impl_tbl = func_tbl[rcvr_type][wk->lang_mode]; if (!impl_tbl) { return; } if (!(fi = func_lookup(impl_tbl, name))) { return; } } if (fi->return_type) { *res = make_typeinfo(wk, fi->return_type, 0); } ++ctx->found; ctx->found_func = fi; return; } static void analyze_index(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, type_tag lhs, obj *res) { switch (lhs) { case obj_disabler: ctx->expected |= tc_any; *res = disabler_id; return; case obj_array: { ctx->expected |= tc_number; *res = make_typeinfo(wk, tc_any, 0); break; } case obj_dict: { ctx->expected |= tc_string; *res = make_typeinfo(wk, tc_any, 0); break; } case obj_custom_target: { ctx->expected |= tc_number; *res = make_typeinfo(wk, tc_file, 0); break; } case obj_string: { ctx->expected |= tc_number; *res = make_typeinfo(wk, tc_string, 0); break; } default: UNREACHABLE; } return; } static bool analyze_chained(struct workspace *wk, uint32_t n_id, obj l_id, obj *res) { bool ret = true; struct node *n = get_node(wk->ast, n_id); mark_node_visited(n); obj tmp = 0; switch (n->type) { case node_method: { struct analyze_ctx ctx = { .l = l_id }; analyze_for_each_type(wk, &ctx, n_id, l_id, 0, analyze_method, &tmp); if (ctx.found == 1) { if (!analyze_function_call(wk, n_id, n->c, ctx.found_func, l_id, &tmp)) { ret = false; } } else if (ctx.found) { analyze_all_function_arguments(wk, n_id, n->c); if (ctx.expected) { tmp = make_typeinfo(wk, ctx.expected, 0); } } else if (!ctx.found) { analyze_all_function_arguments(wk, n_id, n->c); type_tag t = get_obj_type(wk, l_id); bool rcvr_is_not_found_module = (t == obj_module && !get_obj_module(wk, l_id)->found) || (t == obj_typeinfo && get_obj_typeinfo(wk, l_id)->type == tc_module); bool rcvr_is_module_object = t == obj_typeinfo && get_obj_typeinfo(wk, l_id)->subtype == tc_module; if (rcvr_is_not_found_module || rcvr_is_module_object) { tmp = make_typeinfo(wk, tc_any, tc_module); } else { interp_error(wk, n_id, "method %s not found on %s", get_node(wk->ast, n->r)->dat.s, inspect_typeinfo(wk, l_id)); ret = false; tmp = make_typeinfo(wk, tc_any, 0); } } break; } case node_index: { obj r; if (!wk->interp_node(wk, n->r, &r)) { r = make_typeinfo(wk, tc_any, 0); ret = false; } struct analyze_ctx ctx = { 0 }; const type_tag tc_lhs = tc_string | tc_custom_target | tc_dict | tc_array; if (typecheck(wk, n->l, l_id, tc_lhs)) { analyze_for_each_type(wk, &ctx, n_id, l_id, tc_lhs, analyze_index, &tmp); if (typecheck(wk, n->r, r, ctx.expected)) { ret &= true; } if (is_pure_arithmetic_object(wk, l_id) && is_pure_arithmetic_object(wk, r)) { ret &= interp_index(wk, n, l_id, false, &tmp); } } else { tmp = make_typeinfo(wk, tc_any, 0); } break; } default: UNREACHABLE_RETURN; } if (n->chflg & node_child_d) { ret &= analyze_chained(wk, n->d, tmp, res); } else { *res = tmp; } return ret; } static bool analyze_func(struct workspace *wk, uint32_t n_id, obj *res) { bool ret = true; obj tmp = 0; struct node *n = get_node(wk->ast, n_id); const char *name = get_node(wk->ast, n->l)->dat.s; mark_node_visited(get_node(wk->ast, n->l)); const struct func_impl_name *fi; if (!(fi = func_lookup(kernel_func_tbl[wk->lang_mode], name))) { interp_error(wk, n_id, "function %s not found", name); ret = false; analyze_all_function_arguments(wk, n_id, n->r); tmp = make_typeinfo(wk, tc_any, 0); } else { if (!analyze_function_call(wk, n_id, n->r, fi, 0, &tmp)) { ret = false; } } if (n->chflg & node_child_d) { return analyze_chained(wk, n->d, tmp, res); } else { *res = tmp; return ret; } } static bool analyze_u_minus(struct workspace *wk, struct node *n, obj *res) { bool ret = true; obj l_id; if (!wk->interp_node(wk, n->l, &l_id)) { ret = false; } if (ret && !typecheck(wk, n->l, l_id, obj_number)) { ret = false; } if (ret && get_obj_type(wk, l_id) == obj_number) { make_obj(wk, res, obj_number); set_obj_number(wk, *res, -get_obj_number(wk, l_id)); } else { *res = make_typeinfo(wk, tc_number, 0); } return ret; } static void analyze_arithmetic_cb(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, type_tag lhs, obj *res) { switch (lhs) { case obj_string: { ctx->expected |= tc_string; *res = make_typeinfo(wk, tc_string, 0); break; } case obj_number: { ctx->expected |= tc_number; *res = make_typeinfo(wk, tc_number, 0); break; } case obj_array: { ctx->expected |= tc_any; *res = make_typeinfo(wk, tc_array, 0); break; } case obj_dict: { ctx->expected |= tc_dict; *res = make_typeinfo(wk, tc_dict, 0); break; } default: UNREACHABLE; } } static bool analyze_arithmetic(struct workspace *wk, uint32_t err_node, enum arithmetic_type type, bool plusassign, uint32_t nl, uint32_t nr, obj *res) { bool ret = true; obj l, r; if (!wk->interp_node(wk, nl, &l)) { ret = false; } if (!wk->interp_node(wk, nr, &r)) { ret = false; } if (is_pure_arithmetic_object(wk, l) && is_pure_arithmetic_object(wk, r)) { return interp_arithmetic(wk, err_node, type, plusassign, nl, nr, res); } struct analyze_ctx ctx = { 0 }; type_tag tc_lhs = tc_disabler; switch (type) { case arith_add: tc_lhs |= tc_string | tc_number | tc_dict | tc_array; break; case arith_div: tc_lhs |= tc_string | tc_number; break; case arith_sub: case arith_mod: case arith_mul: tc_lhs |= tc_number; break; default: assert(false); return false; } if (ret && !typecheck(wk, nl, l, tc_lhs)) { return false; } if (ret) { analyze_for_each_type(wk, &ctx, nl, l, tc_lhs, analyze_arithmetic_cb, res); } if (ret && !typecheck(wk, nr, r, ctx.expected)) { ret = false; } if (!ret) { *res = make_typeinfo(wk, tc_string | tc_number | tc_array | tc_dict, 0); } return ret; } static bool analyze_not(struct workspace *wk, struct node *n, obj *res) { bool ret = true; obj l; if (!wk->interp_node(wk, n->l, &l)) { ret = false; } if (ret && !typecheck(wk, n->l, l, obj_bool)) { ret = false; } if (ret && get_obj_type(wk, l) == obj_bool) { make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, !get_obj_bool(wk, l)); } else { *res = make_typeinfo(wk, tc_bool, 0); } return ret; } static bool analyze_andor(struct workspace *wk, struct node *n, obj *res) { bool ret = true; obj l, r; if (!wk->interp_node(wk, n->l, &l)) { ret = false; } else if (ret && !typecheck(wk, n->l, l, obj_bool)) { ret = false; } if (is_pure_arithmetic_object(wk, l)) { bool cond = get_obj_bool(wk, l); if (n->type == node_and && !cond) { make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } else if (n->type == node_or && cond) { make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, true); return true; } } if (!wk->interp_node(wk, n->r, &r)) { ret = false; } else if (ret && !typecheck(wk, n->r, r, obj_bool)) { ret = false; } *res = make_typeinfo(wk, tc_bool, 0); return ret; } static void analyze_comparison_cb(struct workspace *wk, struct analyze_ctx *ctx, uint32_t n_id, type_tag lhs, obj *res) { switch (ctx->comparison_type) { case comp_equal: case comp_nequal: ctx->expected |= obj_type_to_tc_type(lhs); break; case comp_in: case comp_not_in: ctx->expected |= tc_array | tc_dict; break; case comp_lt: case comp_le: case comp_gt: case comp_ge: ctx->expected |= tc_number; break; default: UNREACHABLE; } *res = make_typeinfo(wk, tc_bool, 0); } static bool analyze_comparison(struct workspace *wk, struct node *n, obj *res) { bool ret = true; obj l, r; if (!wk->interp_node(wk, n->l, &l)) { ret = false; } if (!wk->interp_node(wk, n->r, &r)) { ret = false; } struct analyze_ctx ctx = { .comparison_type = n->subtype }; type_tag tc_lhs; switch ((enum comparison_type)n->subtype) { case comp_equal: case comp_nequal: tc_lhs = tc_any; break; case comp_in: case comp_not_in: tc_lhs = tc_any; break; case comp_lt: case comp_le: case comp_gt: case comp_ge: tc_lhs = tc_number; break; default: UNREACHABLE_RETURN; } if (ret && !typecheck(wk, n->l, l, tc_lhs)) { ret = false; } if (ret) { analyze_for_each_type(wk, &ctx, n->l, l, tc_lhs, analyze_comparison_cb, res); } if (ret && !typecheck(wk, n->r, r, ctx.expected)) { ret = false; } if (is_pure_arithmetic_object(wk, l) && is_pure_arithmetic_object(wk, r)) { ret = interp_comparison(wk, n, res); } if (!ret) { *res = make_typeinfo(wk, tc_bool, 0); } return ret; } static bool analyze_ternary(struct workspace *wk, struct node *n, obj *res) { bool ret = true; obj cond_id; if (!wk->interp_node(wk, n->l, &cond_id)) { ret = false; } else if (ret && !typecheck(wk, n->l, cond_id, obj_bool)) { ret = false; } bool pure_cond = false, cond = false; if (ret && is_pure_arithmetic_object(wk, cond_id)) { pure_cond = true; cond = get_obj_bool(wk, cond_id); } struct obj_typeinfo res_t = { 0 }; obj a = 0, b = 0; if (!pure_cond || (pure_cond && cond)) { if (!wk->interp_node(wk, n->r, &a)) { ret = false; } if (ret && pure_cond) { *res = a; return true; } } if (!pure_cond || (pure_cond && !cond)) { if (!wk->interp_node(wk, n->c, &b)) { ret = false; } if (ret && pure_cond) { *res = b; return true; } } if (!a && !b) { *res = 0; return ret; } if (a) { merge_types(wk, &res_t, a); } if (b) { merge_types(wk, &res_t, b); } *res = make_typeinfo(wk, res_t.type, res_t.subtype); return ret; } static bool analyze_if(struct workspace *wk, struct node *n, obj *res) { // Push the scope group before evaluating the condition, because the // condition may involve variables that were set in the block of the // previous branch, e.g. // // if a == 1 a = 2 elseif a == 3 a = 3 endif // // If push_scope_group_scope() happened _after_ evaulating the // condition, then inside the expression `elseif a == 2`, `a` would be // looked up in the previous branches scope, leading the analyzer to // wrongly conclude the value `a` is constant 2 in the current scope, // and marking the elsif branch as dead code. push_scope_group_scope(); bool ret = true; bool pure_cond = false, cond = false; switch ((enum if_type)n->subtype) { case if_if: case if_elseif: { obj cond_id; if (!wk->interp_node(wk, n->l, &cond_id)) { ret = false; } else if (!typecheck(wk, n->l, cond_id, obj_bool)) { ret = false; } if (ret && is_pure_arithmetic_object(wk, cond_id)) { cond = get_obj_bool(wk, cond_id); pure_cond = true; } break; } case if_else: cond = true; pure_cond = true; break; default: UNREACHABLE_RETURN; } if (pure_cond && !cond) { // don't evaluate this block } else { // if block if (!wk->interp_node(wk, n->r, res)) { ret = false; } } if (pure_cond && cond) { return true; } if (n->chflg & node_child_c) { if (!wk->interp_node(wk, n->c, res)) { ret = false; } } *res = 0; return ret; } static bool analyze_foreach(struct workspace *wk, uint32_t n_id, obj *res) { obj iterable; bool ret = true; struct node *n = get_node(wk->ast, n_id); if (!wk->interp_node(wk, n->r, &iterable)) { ret = false; iterable = make_typeinfo(wk, tc_array | tc_dict, 0); } if (!typecheck(wk, n->r, iterable, tc_array | tc_dict)) { ret = false; iterable = make_typeinfo(wk, tc_array | tc_dict, 0); } struct node *args = get_node(wk->ast, n->l); type_tag t = get_obj_type(wk, iterable); if (t == obj_typeinfo) { t = get_obj_typeinfo(wk, iterable)->type & (tc_array | tc_dict); } else { t = obj_type_to_tc_type(t); } bool both = t == (tc_array | tc_dict); assert(both || (t == tc_array) || (t == tc_dict)); if (!both) { switch (t) { case tc_array: if (args->chflg & node_child_r) { interp_error(wk, n->l, "array foreach needs exactly one variable to set"); ret = false; } break; case tc_dict: if (!(args->chflg & node_child_r)) { interp_error(wk, n->l, "dict foreach needs exactly two variables to set"); ret = false; } break; default: UNREACHABLE; } } uint32_t n_l = args->l, n_r; if (args->chflg & node_child_r) { // two variables n_r = get_node(wk->ast, args->r)->l; mark_node_visited(get_node(wk->ast, n_l)); mark_node_visited(get_node(wk->ast, n_r)); } else { mark_node_visited(get_node(wk->ast, n_l)); } if (get_obj_type(wk, iterable) != obj_typeinfo) { /* uint32_t len; */ /* switch (get_obj_type(wk, iterable)) { */ /* case obj_dict: */ /* len = get_obj_dict(wk, iterable)->len; */ /* break; */ /* case obj_array: */ /* len = get_obj_array(wk, iterable)->len; */ /* break; */ /* default: */ /* UNREACHABLE; */ /* } */ /* if (len) { */ return interp_node(wk, n_id, res); /* } */ } push_scope_group(); push_scope_group_scope(); if (args->chflg & node_child_r) { // two variables n_r = get_node(wk->ast, args->r)->l; scope_assign(wk, get_node(wk->ast, n_l)->dat.s, make_typeinfo(wk, tc_string, 0), n_l); scope_assign(wk, get_node(wk->ast, n_r)->dat.s, make_typeinfo(wk, tc_any, 0), n_r); } else { scope_assign(wk, get_node(wk->ast, n_l)->dat.s, make_typeinfo(wk, tc_any, 0), n_l); } ++wk->impure_loop_depth; if (!wk->interp_node(wk, n->c, res)) { ret = false; } // interpret a second time to catch variables whose constness are // invalidated by the first iteration, e.g. // i += 1 if (!wk->interp_node(wk, n->c, res)) { ret = false; } --wk->impure_loop_depth; pop_scope_group(wk); return ret; } static bool analyze_stringify(struct workspace *wk, struct node *n, obj *res) { obj l_id; *res = make_typeinfo(wk, tc_string, 0); if (!wk->interp_node(wk, n->l, &l_id)) { return false; } if (!typecheck(wk, n->l, l_id, tc_bool | tc_file | tc_number | tc_string)) { return false; } if (is_pure_arithmetic_object(wk, l_id)) { return interp_stringify(wk, n, res); } return true; } static bool analyze_assign(struct workspace *wk, struct node *n) { bool ret = true; obj rhs; if (!wk->interp_node(wk, n->r, &rhs)) { ret = false; rhs = make_typeinfo(wk, tc_any, 0); } mark_node_visited(get_node(wk->ast, n->l)); scope_assign(wk, get_node(wk->ast, n->l)->dat.s, rhs, n->l); return ret; } static bool analyze_plusassign(struct workspace *wk, uint32_t n_id, obj *_) { struct node *n = get_node(wk->ast, n_id); obj rhs; if (!analyze_arithmetic(wk, n_id, arith_add, true, n->l, n->r, &rhs)) { return false; } scope_assign(wk, get_node(wk->ast, n->l)->dat.s, rhs, n->l); return true; } bool analyze_node(struct workspace *wk, uint32_t n_id, obj *res) { bool ret = true; *res = 0; struct node *n = get_node(wk->ast, n_id); mark_node_visited(n); /* L("analyzing node '%s'@%d", node_to_s(n)); */ if (wk->loop_ctl) { return true; } switch (n->type) { /* literals */ case node_dict: case node_array: case node_string: case node_number: case node_bool: ret = interp_node(wk, n_id, res); break; /* control flow */ case node_block: ret = interp_node(wk, n_id, res); break; case node_if: if (n->subtype == if_if) { push_scope_group(); } ret = analyze_if(wk, n, res); if (n->subtype == if_if) { pop_scope_group(wk); } break; case node_foreach: ret = analyze_foreach(wk, n_id, res); break; case node_continue: ret = true; break; case node_break: ret = true; break; /* functions */ case node_function: ret = analyze_func(wk, n_id, res); break; case node_method: case node_index: { obj l_id; assert(n->chflg & node_child_l); if (!analyze_node(wk, n->l, &l_id)) { ret = false; break; } ret = analyze_chained(wk, n_id, l_id, res); break; } /* assignment */ case node_assignment: ret = analyze_assign(wk, n); break; case node_id: { struct assignment *a; if (!(a = assign_lookup(wk, n->dat.s))) { interp_error(wk, n_id, "undefined object %s", n->dat.s); *res = make_typeinfo(wk, tc_any, 0); ret = false; } else { *res = a->o; a->accessed = true; } break; } /* comparison stuff */ case node_not: ret = analyze_not(wk, n, res); break; case node_and: case node_or: ret = analyze_andor(wk, n, res); break; case node_comparison: ret = analyze_comparison(wk, n, res); break; case node_ternary: ret = analyze_ternary(wk, n, res); break; /* math */ case node_u_minus: ret = analyze_u_minus(wk, n, res); break; case node_arithmetic: ret = analyze_arithmetic(wk, n_id, n->subtype, false, n->l, n->r, res); break; case node_plusassign: ret = analyze_plusassign(wk, n_id, res); break; /* special */ case node_stringify: ret = analyze_stringify(wk, n, res); break; case node_empty: ret = true; break; /* never valid */ case node_foreach_args: case node_argument: case node_paren: case node_empty_line: case node_null: UNREACHABLE_RETURN; } if (!ret) { analyze_error = true; } return true; } void analyze_check_dead_code(struct workspace *wk, struct ast *ast) { if (!analyze_diagnostic_enabled(analyze_diagnostic_dead_code)) { return; } uint32_t i; for (i = 0; i < ast->nodes.len; ++i) { struct node *n = darr_get(&ast->nodes, i); switch (n->type) { case node_foreach_args: case node_paren: case node_empty_line: case node_null: case node_empty: case node_block: case node_argument: case node_id: case node_string: case node_number: continue; /* continue; */ default: break; } if (!(n->chflg & node_visited)) { interp_warning(wk, i, "%s, dead code", node_to_s(n)); if (analyze_entrypoint_stack.len) { uint32_t ep_stacks_i, ep_stack_len; copy_analyze_entrypoint_stack(&ep_stacks_i, &ep_stack_len); mark_analyze_entrypoint_as_containing_diagnostic(ep_stacks_i, log_warn); } } } } static void analyze_assign_wrapper(struct workspace *wk, const char *name, obj o, uint32_t n_id) { scope_assign(wk, name, o, n_id); } static bool analyze_lookup_wrapper(struct workspace *wk, const char *name, obj *res, uint32_t proj_id) { struct assignment *a = assign_lookup(wk, name); if (a) { *res = a->o; return true; } else { return false; } } static bool analyze_eval_project_file(struct workspace *wk, const char *path, bool first) { const char *newpath = path; if (analyze_opts->file_override && strcmp(analyze_opts->file_override, path) == 0) { bool ret = false; struct source src = { 0 }; if (!fs_read_entire_file("-", &src)) { return false; } src.label = path; obj res; if (!eval(wk, &src, first ? eval_mode_first : eval_mode_default, &res)) { goto ret; } ret = true; ret: fs_source_destroy(&src); return ret; } return eval_project_file(wk, newpath, first); } static const struct { const char *name; enum analyze_diagnostic d; } analyze_diagnostic_names[] = { { "unused-variable", analyze_diagnostic_unused_variable }, { "reassign-to-conflicting-type", analyze_diagnostic_reassign_to_conflicting_type }, { "dead-code", analyze_diagnostic_dead_code }, }; bool analyze_diagnostic_name_to_enum(const char *name, enum analyze_diagnostic *ret) { uint32_t i; for (i = 0; i < ARRAY_LEN(analyze_diagnostic_names); ++i) { if (strcmp(analyze_diagnostic_names[i].name, name) == 0) { *ret = analyze_diagnostic_names[i].d; return true; } } return false; } void analyze_print_diagnostic_names(void) { uint32_t i; for (i = 0; i < ARRAY_LEN(analyze_diagnostic_names); ++i) { printf("%s\n", analyze_diagnostic_names[i].name); } } bool do_analyze(struct analyze_opts *opts) { bool res = false; analyze_opts = opts; struct workspace wk; workspace_init(&wk); darr_init(&assignment_scopes.groups, 16, sizeof(struct scope_group)); darr_init(&assignment_scopes.base, 256, sizeof(struct assignment)); { /* * default variables have to be re-assigned with scope_assign * for them to be used in the analyzer */ const char *default_vars[] = { "meson", "host_machine", "build_machine", "target_machine", }; uint32_t i; uint64_t obj; for (i = 0; i < ARRAY_LEN(default_vars); ++i) { obj = *hash_get_str(&wk.scope, default_vars[i]); hash_unset_str(&wk.scope, default_vars[i]); struct assignment *a = scope_assign(&wk, default_vars[i], obj, 0); a->default_var = true; } } wk.interp_node = analyze_node; wk.assign_variable = analyze_assign_wrapper; wk.unassign_variable = analyze_unassign; wk.get_variable = analyze_lookup_wrapper; wk.eval_project_file = analyze_eval_project_file; wk.in_analyzer = true; error_diagnostic_store_init(); darr_init(&analyze_entrypoint_stack, 32, sizeof(struct analyze_file_entrypoint)); darr_init(&analyze_entrypoint_stacks, 32, sizeof(struct analyze_file_entrypoint)); if (analyze_opts->file_override) { const char *root = determine_project_root(&wk, analyze_opts->file_override); if (root) { SBUF(cwd); path_cwd(&wk, &cwd); if (strcmp(cwd.buf, root) != 0) { path_chdir(root); wk.source_root = root; } } } if (opts->internal_file) { uint32_t proj_id; make_project(&wk, &proj_id, "dummy", wk.source_root, wk.build_root); struct assignment *a = scope_assign(&wk, "argv", make_typeinfo(&wk, tc_array, 0), 0); a->default_var = true; wk.lang_mode = language_internal; struct source src; if (!fs_read_entire_file(opts->internal_file, &src)) { res = false; } else { obj _v; res = eval(&wk, &src, eval_mode_default, &_v); } fs_source_destroy(&src); } else { uint32_t project_id; res = eval_project(&wk, NULL, wk.source_root, wk.build_root, &project_id); } if (analyze_diagnostic_enabled(analyze_diagnostic_unused_variable)) { assert(assignment_scopes.groups.len == 0); uint32_t i; for (i = 0; i < assignment_scopes.base.len; ++i) { struct assignment *a = darr_get(&assignment_scopes.base, i); if (!a->default_var && !a->accessed && *a->name != '_') { const char *msg = get_cstr(&wk, make_strf(&wk, "unused variable %s", a->name)); enum log_level lvl = log_warn; error_diagnostic_store_push(a->src_idx, a->line, a->col, lvl, msg); if (analyze_opts->subdir_error && a->ep_stack_len) { mark_analyze_entrypoint_as_containing_diagnostic(a->ep_stacks_i, lvl); } } } } uint32_t i; for (i = 0; i < analyze_entrypoint_stacks.len;) { uint32_t j; struct analyze_file_entrypoint *ep_stack = darr_get(&analyze_entrypoint_stacks, i); assert(ep_stack->is_root); uint32_t len = 1; for (j = 1; j + i < analyze_entrypoint_stacks.len && !ep_stack[j].is_root; ++j) { ++len; } if (ep_stack->has_diagnostic) { enum log_level lvl = ep_stack->lvl; for (j = 0; j < len; ++j) { error_diagnostic_store_push( ep_stack[j].src_idx, ep_stack[j].line, ep_stack[j].col, lvl, "in subdir" ); } } i += len; } bool saw_error; error_diagnostic_store_replay(analyze_opts->replay_opts, &saw_error); if (saw_error || analyze_error) { res = false; } darr_destroy(&analyze_entrypoint_stack); darr_destroy(&analyze_entrypoint_stacks); darr_destroy(&assignment_scopes.groups); darr_destroy(&assignment_scopes.base); workspace_destroy(&wk); return res; } muon-0.2.0/src/lang/eval.c000066400000000000000000000215341441402162300152740ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include "error.h" #include "external/bestline.h" #include "lang/analyze.h" #include "lang/eval.h" #include "lang/interpreter.h" #include "lang/parser.h" #include "log.h" #include "options.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "platform/path.h" #include "tracy.h" #include "wrap.h" bool eval_project(struct workspace *wk, const char *subproject_name, const char *cwd, const char *build_dir, uint32_t *proj_id) { SBUF(src); path_join(wk, &src, cwd, "meson.build"); bool ret = false; uint32_t parent_project = wk->cur_project; make_project(wk, &wk->cur_project, subproject_name, cwd, build_dir); *proj_id = wk->cur_project; const char *parent_prefix = log_get_prefix(); char log_prefix[256] = { 0 }; if (wk->cur_project > 0) { const char *clr = log_clr() ? "\033[35m" : "", *no_clr = log_clr() ? "\033[0m" : ""; snprintf(log_prefix, 255, "[%s%s%s]", clr, subproject_name, no_clr); log_set_prefix(log_prefix); } if (subproject_name) { LOG_I("entering subproject '%s'", subproject_name); } if (!setup_project_options(wk, cwd)) { goto cleanup; } if (!wk->eval_project_file(wk, src.buf, true)) { goto cleanup; } if (wk->cur_project == 0 && !check_invalid_subproject_option(wk)) { goto cleanup; } ret = true; cleanup: wk->cur_project = parent_project; log_set_prefix(parent_prefix); return ret; } static bool ensure_project_is_first_statement(struct workspace *wk, struct ast *ast, bool check_only) { uint32_t err_node; bool first_statement_is_a_call_to_project = false; struct node *n; err_node = ast->root; n = get_node(ast, ast->root); if (!(n->type == node_block && n->chflg & node_child_l)) { goto err; } err_node = n->l; n = get_node(ast, n->l); if (!(n->type == node_function)) { goto err; } err_node = n->l; n = get_node(ast, n->l); if (!(n->type == node_id && strcmp(n->dat.s, "project") == 0)) { goto err; } first_statement_is_a_call_to_project = true; err: if (!first_statement_is_a_call_to_project) { if (!check_only) { interp_error(wk, err_node, "first statement is not a call to project()"); } return false; } return true; } bool eval(struct workspace *wk, struct source *src, enum eval_mode mode, obj *res) { TracyCZoneAutoS; /* L("evaluating '%s'", src->label); */ interpreter_init(); bool ret = false; struct ast ast = { 0 }; struct source_data *sdata = darr_get(&wk->source_data, darr_push(&wk->source_data, &(struct source_data) { 0 })); enum parse_mode parse_mode = 0; if (mode == eval_mode_repl) { parse_mode |= pm_ignore_statement_with_no_effect; } if (!parser_parse(wk, &ast, sdata, src, parse_mode)) { goto ret; } struct source *old_src = wk->src; struct ast *old_ast = wk->ast; wk->src = src; wk->ast = * if (mode == eval_mode_first) { if (!ensure_project_is_first_statement(wk, &ast, false)) { goto ret; } } ret = wk->interp_node(wk, wk->ast->root, res); if (wk->subdir_done) { wk->subdir_done = false; } if (wk->in_analyzer) { analyze_check_dead_code(wk, &ast); } wk->src = old_src; wk->ast = old_ast; ret: ast_destroy(&ast); TracyCZoneAutoE; return ret; } bool eval_str(struct workspace *wk, const char *str, enum eval_mode mode, obj *res) { struct source src = { .label = "", .src = str, .len = strlen(str) }; return eval(wk, &src, mode, res); } bool eval_project_file(struct workspace *wk, const char *path, bool first) { /* L("evaluating '%s'", path); */ bool ret = false; workspace_add_regenerate_deps(wk, make_str(wk, path)); struct source src = { 0 }; if (!fs_read_entire_file(path, &src)) { return false; } obj res; if (!eval(wk, &src, first ? eval_mode_first : eval_mode_default, &res)) { goto ret; } ret = true; ret: fs_source_destroy(&src); return ret; } void source_data_destroy(struct source_data *sdata) { if (sdata->data) { z_free(sdata->data); } } static bool repl_eval_str(struct workspace *wk, const char *str, obj *repl_res) { bool ret, o_break_on_err = wk->dbg.break_on_err; wk->dbg.break_on_err = false; ret = eval_str(wk, str, eval_mode_repl, repl_res); wk->dbg.break_on_err = o_break_on_err; return ret; } void repl(struct workspace *wk, bool dbg) { bool loop = true; obj repl_res = 0; char *line; FILE *out = stderr; enum repl_cmd { repl_cmd_noop, repl_cmd_exit, repl_cmd_abort, repl_cmd_step, repl_cmd_list, repl_cmd_inspect, repl_cmd_watch, repl_cmd_unwatch, repl_cmd_help, }; enum repl_cmd cmd = repl_cmd_noop; struct { const char *name[3]; enum repl_cmd cmd; bool valid, has_arg; const char *help_text; } repl_cmds[] = { { { "abort", 0 }, repl_cmd_abort, dbg }, { { "c", "continue", 0 }, repl_cmd_exit, dbg }, { { "exit", 0 }, repl_cmd_exit, !dbg }, { { "h", "help", 0 }, repl_cmd_help, true }, { { "i", "inspect", 0 }, repl_cmd_inspect, dbg, true, .help_text = "\\inspect " }, { { "l", "list", 0 }, repl_cmd_list, dbg }, { { "s", "step", 0 }, repl_cmd_step, dbg }, { { "w", "watch", 0 }, repl_cmd_watch, dbg, true }, { { "uw", "unwatch", 0 }, repl_cmd_unwatch, dbg, true }, 0 }; if (dbg) { list_line_range(wk->src, get_node(wk->ast, wk->dbg.node)->line, 1); if (wk->dbg.stepping) { cmd = repl_cmd_step; } } const char *prompt = "> ", cmd_char = '\\'; while (loop && (line = muon_bestline(prompt))) { muon_bestline_history_add(line); if (!*line || *line == cmd_char) { char *arg = NULL; if (!*line || !line[1]) { goto cmd_found; } if ((arg = strchr(line, ' '))) { *arg = 0; ++arg; } uint32_t i, j; for (i = 0; *repl_cmds[i].name; ++i) { if (repl_cmds[i].valid) { for (j = 0; repl_cmds[i].name[j]; ++j) { if (strcmp(&line[1], repl_cmds[i].name[j]) == 0) { if (repl_cmds[i].has_arg) { if (!arg) { fprintf(out, "missing argument\n"); goto cont; } } else { if (arg) { fprintf(out, "this command does not take an argument\n"); goto cont; } } cmd = repl_cmds[i].cmd; goto cmd_found; } } } } fprintf(out, "unknown repl command '%s'\n", &line[1]); goto cont; cmd_found: switch (cmd) { case repl_cmd_abort: exit(1); break; case repl_cmd_exit: wk->dbg.stepping = false; loop = false; break; case repl_cmd_help: fprintf(out, "repl commands:\n"); for (i = 0; *repl_cmds[i].name; ++i) { if (!repl_cmds[i].valid) { continue; } fprintf(out, " - "); for (j = 0; repl_cmds[i].name[j]; ++j) { fprintf(out, "%c%s", cmd_char, repl_cmds[i].name[j]); if (repl_cmds[i].name[j + 1]) { fprintf(out, ", "); } } if (repl_cmds[i].help_text) { fprintf(out, " - %s", repl_cmds[i].help_text); } fprintf(out, "\n"); } break; case repl_cmd_list: { list_line_range(wk->src, get_node(wk->ast, wk->dbg.node)->line, 11); break; } case repl_cmd_step: wk->dbg.stepping = true; loop = false; break; case repl_cmd_inspect: if (!repl_eval_str(wk, arg, &repl_res)) { break; } obj_inspect(wk, out, repl_res); break; case repl_cmd_watch: if (!wk->dbg.watched) { make_obj(wk, &wk->dbg.watched, obj_array); } obj_array_push(wk, wk->dbg.watched, make_str(wk, arg)); break; case repl_cmd_unwatch: if (wk->dbg.watched) { uint32_t idx; if (obj_array_index_of(wk, wk->dbg.watched, make_str(wk, arg), &idx)) { obj_array_del(wk, wk->dbg.watched, idx); } } break; case repl_cmd_noop: break; } } else { cmd = repl_cmd_noop; if (!repl_eval_str(wk, line, &repl_res)) { goto cont; } if (repl_res) { obj_fprintf(wk, out, "%o\n", repl_res); hash_set_str(&wk->scope, "_", repl_res); } } cont: muon_bestline_free(line); } muon_bestline_history_free(); if (!line) { wk->dbg.stepping = false; } } const char * determine_project_root(struct workspace *wk, const char *path) { SBUF(tmp); SBUF(new_path); path_make_absolute(wk, &new_path, path); path = new_path.buf; while (true) { if (!fs_file_exists(path)) { goto cont; } struct ast ast = { 0 }; struct source src = { 0 }; struct source_data sdata = { 0 }; if (!fs_read_entire_file(path, &src)) { return NULL; } else if (!parser_parse(wk, &ast, &sdata, &src, pm_quiet)) { return NULL; } wk->src = &src; wk->ast = * if (ensure_project_is_first_statement(wk, &ast, true)) { // found path_dirname(wk, &tmp, path); obj s = sbuf_into_str(wk, &tmp); return get_cstr(wk, s); } cont: path_dirname(wk, &tmp, path); path_dirname(wk, &new_path, tmp.buf); if (strcmp(new_path.buf, tmp.buf) == 0) { return NULL; } path_push(wk, &new_path, "meson.build"); path = new_path.buf; } } muon-0.2.0/src/lang/fmt.c000066400000000000000000000601031441402162300151260ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Ailin Nemui * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include "buf_size.h" #include "error.h" #include "formats/ini.h" #include "lang/fmt.h" #include "lang/string.h" #include "log.h" #include "platform/mem.h" struct arg_elem { uint32_t kw, val, next; }; struct fmt_ctx { struct ast *ast; struct sbuf *out_buf; uint32_t indent, col, enclosed; bool force_ml; bool trailing_comment; /* options */ bool space_array, kwa_ml, wide_colon, no_single_comma_function; uint32_t max_line_len; const char *indent_by; }; enum special_fmt { special_fmt_sort_files = 1 << 0, special_fmt_collapse_lone_array_arg = 1 << 1, special_fmt_cmd_array = 1 << 2, }; struct fmt_stack { uint32_t parent; const char *node_sep; const char *arg_container; enum special_fmt special_fmt; bool write; bool ml; bool fmt_and_or_force_ml; }; typedef uint32_t ((*fmt_func)(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id)); static uint32_t fmt_node(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id); static uint32_t fmt_chain(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id); static const char * get_comment(struct fmt_ctx *ctx, struct node *n, uint32_t i) { return *(const char **)darr_get(&ctx->ast->comments, n->comments.start + i); } static bool fmt_str_startwith(struct fmt_ctx *ctx, uint32_t n_id, const char *pre) { struct node *n = get_node(ctx->ast, n_id); if (n->type != node_string) { return false; } const char *s = n->dat.s; if (*s == 'f') { ++s; } if (strncmp(s, "'''", 3) == 0) { s += 3; } else { ++s; } return strncmp(s, pre, strlen(pre)) == 0; } static bool fmt_id_eql(struct fmt_ctx *ctx, uint32_t n_id, const char *id) { struct node *n = get_node(ctx->ast, n_id); if (n->type != node_id) { return false; } return strcmp(n->dat.s, id) == 0; } static struct fmt_stack * fmt_setup_fst(const struct fmt_stack *pfst) { static struct fmt_stack fst; fst = *pfst; fst.node_sep = NULL; return &fst; } static uint32_t fmt_check(struct fmt_ctx *ctx, const struct fmt_stack *pfst, fmt_func func, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); fst.node_sep = pfst->node_sep; if (!fst.write) { return func(ctx, &fst, n_id); } bool old_force_ml = ctx->force_ml; ctx->force_ml = false; struct fmt_stack tmp = fst; tmp.write = false; tmp.ml = false; uint32_t len = func(ctx, &tmp, n_id); fst.ml = ctx->force_ml || (len + ctx->col > ctx->max_line_len); len = func(ctx, &fst, n_id); ctx->force_ml = old_force_ml; return len; } static uint32_t fmt_write(struct fmt_ctx *ctx, const struct fmt_stack *pfst, char c) { uint32_t len; assert(c != '\n'); if (c == '\t') { len = 8; } else { len = 1; } if (pfst->write) { sbuf_push(NULL, ctx->out_buf, c); ctx->col += len; } return len; } static uint32_t fmt_writeml(struct fmt_ctx *ctx, const struct fmt_stack *pfst, const char *s) { uint32_t len = 0; for (; *s; ++s) { if (*s == '\n') { if (pfst->write) { sbuf_push(NULL, ctx->out_buf, '\n'); ctx->col = 0; } ctx->force_ml = true; } else { len += fmt_write(ctx, pfst, *s); } } return len; } static uint32_t fmt_writes(struct fmt_ctx *ctx, const struct fmt_stack *pfst, const char *s) { uint32_t len = 0; for (; *s; ++s) { len += fmt_write(ctx, pfst, *s); } return len; } static void fmt_newline(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t next) { uint32_t i; if (pfst->ml) { ctx->trailing_comment = false; } if (pfst->write && pfst->ml) { sbuf_push(NULL, ctx->out_buf, '\n'); ctx->col = 0; if (next != 0) { struct node *n = get_node(ctx->ast, next); if (n->type == node_block) { n = get_node(ctx->ast, n->l); } if (n->type == node_empty_line && !n->comments.len) { return; } } for (i = 0; i < ctx->indent; ++i) { fmt_writes(ctx, pfst, ctx->indent_by); } } } static void fmt_newline_force(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t next) { struct fmt_stack fst = *fmt_setup_fst(pfst); fst.ml = true; fmt_newline(ctx, &fst, next); } static uint32_t fmt_newline_or_space(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t next) { if (pfst->ml) { fmt_newline(ctx, pfst, next); return 0; } else { return fmt_write(ctx, pfst, ' '); } } static uint32_t fmt_breaking_space(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t next) { if (ctx->col >= ctx->max_line_len) { fmt_newline_force(ctx, pfst, next); ctx->force_ml = true; return 0; } else { return fmt_write(ctx, pfst, ' '); } } static void fmt_begin_block(struct fmt_ctx *ctx) { ++ctx->indent; } static void fmt_end_block(struct fmt_ctx *ctx) { --ctx->indent; } MUON_ATTR_FORMAT(printf, 3, 4) static uint32_t fmt_writef(struct fmt_ctx *ctx, const struct fmt_stack *pfst, const char *fmt, ...) { uint32_t len; va_list args; va_start(args, fmt); static char buf[BUF_SIZE_4k]; if (pfst->write) { vsnprintf(buf, BUF_SIZE_4k, fmt, args); len = fmt_writes(ctx, pfst, buf); } else { len = vsnprintf(NULL, 0, fmt, args); } va_end(args); return len; } static uint32_t fmt_comments(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id, bool allow_leading_space) { struct node *n = get_node(ctx->ast, n_id); uint32_t len = 0; if (!n->comments.len) { return 0; } bool leading_space = allow_leading_space && n->type != node_empty_line; ctx->force_ml = true; uint32_t i; for (i = 0; i < n->comments.len; ++i) { len += fmt_writef(ctx, pfst, "%s#%s", leading_space ? " " : "", get_comment(ctx, n, i)); if (i < n->comments.len - 1) { // && !trailing_line) { fmt_newline_force(ctx, pfst, n_id); } else { if (pfst->write) { ctx->trailing_comment = true; } } leading_space = false; } return len; } static uint32_t fmt_check_trailing_comment_or_space(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { if (ctx->trailing_comment) { fmt_newline_force(ctx, pfst, n_id); return 0; } else { fmt_write(ctx, pfst, ' '); return 1; } } static uint32_t fmt_tail(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { uint32_t len = 0; if (pfst->node_sep) { len += fmt_writes(ctx, pfst, pfst->node_sep); } len += fmt_comments(ctx, pfst, n_id, true); return len; } static int32_t fmt_files_args_sort_cmp(const void *a, const void *b, void *_ctx) { struct fmt_ctx *ctx = _ctx; const struct arg_elem *ae1 = a, *ae2 = b; struct node *v1 = get_node(ctx->ast, ae1->val), *v2 = get_node(ctx->ast, ae2->val); if (v1->type == node_string && v2->type == node_string) { const char *s1 = v1->dat.s, *s2 = v2->dat.s; bool s1_hasdir = strchr(s1, '/') != NULL, s2_hasdir = strchr(s2, '/') != NULL; if ((s1_hasdir && s2_hasdir) || (!s1_hasdir && !s2_hasdir)) { return strcmp(v1->dat.s, v2->dat.s); } else if (s1_hasdir) { return -1; } else { return 1; } } else if (v1->type == node_string && v2->type != node_string) { return -1; } else if (v1->type != node_string && v2->type == node_string) { return 1; } else { return 0; } } static uint32_t fmt_args(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_args) { struct fmt_stack fst = *fmt_setup_fst(pfst); fst.node_sep = pfst->node_sep; uint32_t len = 0, kwa = 0; struct node *arg = get_node(ctx->ast, n_args), *arg_val; bool last = false; if (fst.special_fmt & special_fmt_collapse_lone_array_arg) { assert(arg->type != node_empty); struct node *arr = get_node(ctx->ast, arg->l); assert(arr->type == node_array); arg = get_node(ctx->ast, arr->l); } if (arg->type == node_empty) { return 0; } ++ctx->enclosed; fmt_begin_block(ctx); if (pfst->arg_container[0] == '[' && ctx->space_array) { fmt_newline_or_space(ctx, &fst, n_args); } else { fmt_newline(ctx, &fst, n_args); } struct darr args; darr_init(&args, 64, sizeof(struct arg_elem)); while (arg->type != node_empty) { darr_push(&args, &(struct arg_elem) { 0 }); struct arg_elem *ae = darr_get(&args, args.len - 1); if (arg->subtype == arg_kwarg) { ae->kw = arg->l; ae->val = arg->r; ++kwa; } else { ae->kw = 0; ae->val = arg->l; } arg_val = get_node(ctx->ast, ae->val); { // deal with empty lines and comment lines if (arg_val->type == node_empty_line) { ctx->force_ml = true; } } if (arg->chflg & node_child_c) { arg = get_node(ctx->ast, arg->c); ae->next = arg->subtype == arg_kwarg ? arg->r : arg->l; // this means there was a trailing comma if (arg->type == node_empty) { ctx->force_ml = true; } } else { break; } } if (kwa > 1 && ctx->kwa_ml) { ctx->force_ml = true; } if (fst.special_fmt & special_fmt_sort_files) { darr_sort(&args, ctx, fmt_files_args_sort_cmp); } uint32_t i; for (i = 0; i < args.len; ++i) { struct arg_elem *ae = darr_get(&args, i); fst.special_fmt = 0; last = i == args.len - 1; struct node *n = get_node(ctx->ast, ae->val); if (ae->kw) { if (fmt_id_eql(ctx, ae->kw, "command")) { fst.special_fmt |= special_fmt_cmd_array; } fst.node_sep = ctx->wide_colon ? " : " : ": "; len += fmt_node(ctx, &fst, ae->kw); } bool need_comma; if (n->type == node_empty_line) { // never put commas on empty lines need_comma = false; } else if (pfst->arg_container[0] == '(' && ctx->no_single_comma_function && args.len == 1 && !ae->kw) { need_comma = false; } else if (last && !fst.ml) { need_comma = false; } else { need_comma = true; } fst.node_sep = need_comma ? "," : NULL; len += fmt_check(ctx, &fst, fmt_node, ae->val); if (!last) { if ((pfst->special_fmt & special_fmt_cmd_array) && fmt_str_startwith(ctx, ae->val, "-") && !fmt_str_startwith(ctx, ae->next, "-")) { len += fmt_write(ctx, &fst, ' '); } else { len += fmt_newline_or_space(ctx, &fst, ae->next); } } else { break; } } darr_destroy(&args); --ctx->enclosed; fmt_end_block(ctx); if (pfst->arg_container[1] == ']' && ctx->space_array) { fmt_newline_or_space(ctx, &fst, 0); }else { fmt_newline(ctx, &fst, 0); } return len; } static uint32_t fmt_arg_container(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); /* struct node *p = get_node(ctx->ast, fst.parent); */ uint32_t len = 0; len += fmt_write(ctx, &fst, fst.arg_container[0]); len += fmt_comments(ctx, pfst, fst.parent, true); /* if (p->comment) { */ /* len += fmt_writef(ctx, pfst, " #%s", p->comment); */ /* ctx->force_ml = true; */ /* } */ len += fmt_args(ctx, &fst, n_id); len += fmt_write(ctx, &fst, fst.arg_container[1]); return len; } static uint32_t fmt_function_common(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_name, uint32_t n_args) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0; const char *name = get_node(ctx->ast, n_name)->dat.s; len += fmt_writes(ctx, &fst, name); fst.arg_container = "()"; fst.parent = n_name; len += fmt_arg_container(ctx, &fst, n_args); return len; } static uint32_t fmt_function(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); fst.special_fmt = 0; uint32_t len = 0; struct node *f = get_node(ctx->ast, n_id); if (fmt_id_eql(ctx, f->l, "files")) { // fire only if an array is the lone argument struct node *arg = get_node(ctx->ast, f->r); if (arg->type != node_empty && arg->subtype != arg_kwarg && (!(arg->chflg & node_child_c) || get_node(ctx->ast, arg->c)->type == node_empty) && get_node(ctx->ast, arg->l)->type == node_array ) { fst.special_fmt |= special_fmt_collapse_lone_array_arg; } fst.special_fmt |= special_fmt_sort_files; } len += fmt_function_common(ctx, &fst, f->l, f->r); if (f->chflg & node_child_d) { len += fmt_chain(ctx, &fst, f->d); } return len; } static uint32_t fmt_method(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0; struct node *f = get_node(ctx->ast, n_id); len += fmt_function_common(ctx, &fst, f->r, f->c); return len; } static uint32_t fmt_node_wrapped(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0; bool node_needs_paren; switch (get_node(ctx->ast, n_id)->type) { case node_method: case node_array: case node_dict: case node_function: case node_index: case node_paren: case node_string: case node_number: node_needs_paren = false; break; default: node_needs_paren = true; break; } node_needs_paren &= ctx->enclosed == 0; if (fst.ml && node_needs_paren) { ++ctx->enclosed; len += fmt_write(ctx, &fst, '('); fmt_begin_block(ctx); fmt_newline(ctx, &fst, n_id); } len += fmt_node(ctx, &fst, n_id); if (fst.ml && node_needs_paren) { --ctx->enclosed; fmt_end_block(ctx); fmt_newline(ctx, &fst, 0); len += fmt_write(ctx, &fst, ')'); } return len; } static uint32_t fmt_parens(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0; struct node *n = get_node(ctx->ast, n_id); len += fmt_write(ctx, &fst, '('); ++ctx->enclosed; if (fst.ml) { fmt_begin_block(ctx); fmt_newline(ctx, &fst, n->l); } len += fmt_comments(ctx, &fst, n_id, false); len += fmt_node(ctx, &fst, n->l); if (fst.ml) { fmt_end_block(ctx); fmt_newline(ctx, &fst, 0); } --ctx->enclosed; len += fmt_write(ctx, &fst, ')'); return len; } static void fmt_if(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id, bool first) { struct fmt_stack fst = *fmt_setup_fst(pfst); struct node *n = get_node(ctx->ast, n_id); if (n->subtype == if_if || n->subtype == if_elseif) { fmt_writef(ctx, &fst, "%s ", first ? "if" : "elif"); fmt_check(ctx, &fst, fmt_node_wrapped, n->l); } else { fmt_writes(ctx, &fst, "else"); } struct node *block = get_node(ctx->ast, n->r); if (block->type == node_empty) { fmt_comments(ctx, pfst, n->r, true); fmt_newline_force(ctx, &fst, n->r); } else { fmt_begin_block(ctx); fmt_newline_force(ctx, &fst, n->r); fmt_node(ctx, &fst, n->r); fmt_end_block(ctx); fmt_newline_force(ctx, &fst, n->c); } if (n->chflg & node_child_c) { fmt_if(ctx, &fst, n->c, false); } } static uint32_t fmt_chain(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0, tail_id; struct node *n = get_node(ctx->ast, n_id); switch (n->type) { case node_method: len += fmt_write(ctx, pfst, '.'); len += fmt_check(ctx, &fst, fmt_method, n_id); tail_id = n->c; break; case node_index: len += fmt_write(ctx, pfst, '['); ++ctx->enclosed; len += fmt_check(ctx, &fst, fmt_node, n->r); --ctx->enclosed; len += fmt_write(ctx, pfst, ']'); tail_id = n_id; break; default: UNREACHABLE_RETURN; } if (n->chflg & node_child_d) { len += fmt_chain(ctx, pfst, n->d); } else { len += fmt_tail(ctx, pfst, tail_id); } return len; } static uint32_t fmt_and_or(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); fst.fmt_and_or_force_ml = pfst->fmt_and_or_force_ml; struct node *n = get_node(ctx->ast, n_id); uint32_t len = 0; len += fmt_check(ctx, &fst, fmt_node, n->l); if ((ctx->enclosed && fst.ml) || fst.fmt_and_or_force_ml) { fmt_newline_force(ctx, &fst, 0); fst.fmt_and_or_force_ml = true; } else { len += fmt_writes(ctx, &fst, " "); } len += fmt_writes(ctx, &fst, n->type == node_or ? "or " : "and "); len += fmt_check(ctx, &fst, fmt_node, n->r); return len; } static uint32_t fmt_node(struct fmt_ctx *ctx, const struct fmt_stack *pfst, uint32_t n_id) { struct fmt_stack fst = *fmt_setup_fst(pfst); uint32_t len = 0; struct node *n = get_node(ctx->ast, n_id); if (ctx->trailing_comment && n->type != node_empty_line) { fmt_newline_force(ctx, pfst, n_id); } /* if (pfst->write) { */ /* L("formatting %s", node_to_s(n)); */ /* } */ switch (n->type) { /* literals */ case node_bool: len += fmt_writes(ctx, &fst, n->subtype ? "true" : "false"); break; case node_string: len += fmt_writeml(ctx, &fst, n->dat.s); break; case node_array: case node_dict: fst.arg_container = n->type == node_array ? "[]" : "{}"; fst.parent = n_id; n_id = n->l; n = get_node(ctx->ast, n_id); len += fmt_check(ctx, &fst, fmt_arg_container, n_id); break; case node_id: len += fmt_writes(ctx, &fst, n->dat.s); break; case node_number: len += fmt_writes(ctx, &fst, n->dat.s); break; /* control flow */ case node_block: { len = fmt_check(ctx, &fst, fmt_node, n->l); if (n->chflg & node_child_r) { struct node *bnext = get_node(ctx->ast, n->r); if (bnext->type != node_empty) { fmt_newline_force(ctx, &fst, n->r); fmt_node(ctx, &fst, n->r); } } break; } case node_if: { fmt_if(ctx, &fst, n_id, true); fmt_writes(ctx, &fst, "endif"); break; } case node_foreach_args: len += fmt_node(ctx, &fst, n->l); if (n->chflg & node_child_r) { len += fmt_writes(ctx, &fst, ", "); len += fmt_node(ctx, &fst, n->r); } break; case node_foreach: fmt_writes(ctx, &fst, "foreach "); fmt_node(ctx, &fst, n->l); fmt_writes(ctx, &fst, " : "); fmt_check(ctx, &fst, fmt_node_wrapped, n->r); struct node *block = get_node(ctx->ast, n->c); if (block->type == node_empty) { fmt_comments(ctx, pfst, n->c, true); fmt_newline_force(ctx, &fst, n->r); } else { fmt_begin_block(ctx); fmt_newline_force(ctx, &fst, n->c); fmt_node(ctx, &fst, n->c); fmt_end_block(ctx); fmt_newline_force(ctx, &fst, 0); } fmt_writes(ctx, &fst, "endforeach"); break; case node_continue: len += fmt_writes(ctx, &fst, "continue"); break; case node_break: len += fmt_writes(ctx, &fst, "break"); break; /* functions */ case node_function: len += fmt_check(ctx, &fst, fmt_function, n_id); break; case node_method: len += fmt_node(ctx, &fst, n->l); return len + fmt_chain(ctx, pfst, n_id); case node_index: assert(n->chflg & node_child_l); len += fmt_node(ctx, &fst, n->l); return len + fmt_chain(ctx, pfst, n_id); /* assignment */ case node_assignment: len += fmt_node(ctx, &fst, n->l); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_writes(ctx, &fst, "= "); len += fmt_node_wrapped(ctx, &fst, n->r); break; case node_plusassign: len += fmt_node(ctx, &fst, n->l); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_writes(ctx, &fst, "+= "); len += fmt_node_wrapped(ctx, &fst, n->r); break; /* comparison stuff */ case node_not: len += fmt_writes(ctx, &fst, "not "); len += fmt_node(ctx, &fst, n->l); break; case node_and: case node_or: len += fmt_and_or(ctx, &fst, n_id); break; case node_comparison: { assert(n->subtype <= comp_not_in); const char *kw = (char *[]){ [comp_equal] = "==", [comp_nequal] = "!=", [comp_lt] = "<", [comp_le] = "<=", [comp_gt] = ">", [comp_ge] = ">=", [comp_in] = "in", [comp_not_in] = "not in", }[n->subtype]; len += fmt_node(ctx, &fst, n->l); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_writef(ctx, &fst, "%s ", kw); len += fmt_node(ctx, &fst, n->r); break; } case node_ternary: len += fmt_node(ctx, &fst, n->l); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_writef(ctx, &fst, "? "); len += fmt_node(ctx, &fst, n->r); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_writef(ctx, &fst, ": "); len += fmt_node(ctx, &fst, n->c); break; /* math */ case node_u_minus: len += fmt_write(ctx, &fst, '-'); len += fmt_node(ctx, &fst, n->l); break; case node_arithmetic: len += fmt_node(ctx, &fst, n->l); len += fmt_check_trailing_comment_or_space(ctx, &fst, n->l); len += fmt_write(ctx, &fst, "+-%*/"[n->subtype]); len += fmt_breaking_space(ctx, &fst, n->r); len += fmt_node(ctx, &fst, n->r); break; /* formatting */ case node_paren: len += fmt_check(ctx, &fst, fmt_parens, n_id); n_id = n->r; break; case node_empty_line: break; /* handled in other places */ case node_argument: assert(false && "unreachable"); break; case node_empty: break; /* never valid */ case node_stringify: case node_null: assert(false); break; } len += fmt_tail(ctx, pfst, n_id); return len; } static bool fmt_cfg_parse_cb(void *_ctx, struct source *src, const char *sect, const char *k, const char *v, uint32_t line) { struct fmt_ctx *ctx = _ctx; enum val_type { type_uint, type_str, type_bool, }; static const struct { const char *name; enum val_type type; uint32_t off; } keys[] = { { "max_line_len", type_uint, offsetof(struct fmt_ctx, max_line_len) }, { "indent_by", type_str, offsetof(struct fmt_ctx, indent_by) }, { "space_array", type_bool, offsetof(struct fmt_ctx, space_array) }, { "kwargs_force_multiline", type_bool, offsetof(struct fmt_ctx, kwa_ml) }, { "kwa_ml", type_bool, offsetof(struct fmt_ctx, kwa_ml) }, // kept for backwards compat { "wide_colon", type_bool, offsetof(struct fmt_ctx, wide_colon) }, { "no_single_comma_function", type_bool, offsetof(struct fmt_ctx, no_single_comma_function) }, 0 }; if (!k || !*k) { error_messagef(src, line, 1, log_error, "missing key"); return false; } else if (!v || !*v) { error_messagef(src, line, 1, log_error, "missing value"); return false; } else if (sect) { error_messagef(src, line, 1, log_error, "invalid section"); return false; } uint32_t i; for (i = 0; keys[i].name; ++i) { if (strcmp(k, keys[i].name) == 0) { void *val_dest = ((uint8_t *)ctx + keys[i].off); switch (keys[i].type) { case type_uint: { char *endptr = NULL; long lval = strtol(v, &endptr, 10); if (*endptr) { error_messagef(src, line, 1, log_error, "unable to parse integer"); return false; } else if (lval < 0 || lval > (long)UINT32_MAX) { error_messagef(src, line, 1, log_error, "integer outside of range 0-%u", UINT32_MAX); return false; } uint32_t val = lval; memcpy(val_dest, &val, sizeof(uint32_t)); break; } case type_str: { char *start, *end; start = strchr(v, '\''); end = strrchr(v, '\''); if (!start || !end || start == end) { error_messagef(src, line, 1, log_error, "expected single-quoted string"); return false; } *end = 0; ++start; memcpy(val_dest, &start, sizeof(char *)); break; } case type_bool: { bool val; if (strcmp(v, "true") == 0) { val = true; } else if (strcmp(v, "false") == 0) { val = false; } else { error_messagef(src, line, 1, log_error, "invalid value for bool, expected true/false"); return false; } memcpy(val_dest, &val, sizeof(bool)); break; } } } } return true; } bool fmt(struct source *src, FILE *out, const char *cfg_path, bool check_only) { bool ret = false; struct ast ast = { 0 }; struct source_data sdata = { 0 }; struct sbuf out_buf; struct fmt_ctx ctx = { .ast = &ast, .out_buf = &out_buf, .max_line_len = 80, .indent_by = " ", .space_array = false, .kwa_ml = false, .wide_colon = false, .no_single_comma_function = false, }; struct fmt_stack fst = { .write = true, }; if (check_only) { sbuf_init(&out_buf, NULL, 0, sbuf_flag_overflow_alloc); } else { out_buf.flags = sbuf_flag_write; out_buf.buf = (void *)out; } char *cfg_buf = NULL; struct source cfg_src = { 0 }; if (cfg_path) { if (!fs_read_entire_file(cfg_path, &cfg_src)) { goto ret; } else if (!ini_parse(cfg_path, &cfg_src, &cfg_buf, fmt_cfg_parse_cb, &ctx)) { goto ret; } } if (!parser_parse(NULL, &ast, &sdata, src, pm_keep_formatting | pm_ignore_statement_with_no_effect)) { goto ret; } fmt_node(&ctx, &fst, ast.root); assert(!ctx.indent); fmt_newline_force(&ctx, &fst, 0); if (check_only) { if (src->len != out_buf.len) { goto ret; } else if (memcmp(src->src, out_buf.buf, src->len)) { goto ret; } } ret = true; ret: if (cfg_buf) { z_free(cfg_buf); } sbuf_destroy(&out_buf); fs_source_destroy(&cfg_src); ast_destroy(&ast); source_data_destroy(&sdata); return ret; } muon-0.2.0/src/lang/interpreter.c000066400000000000000000000654251441402162300167170ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include "buf_size.h" #include "coerce.h" #include "data/hash.h" #include "error.h" #include "functions/common.h" #include "functions/kernel.h" #include "lang/eval.h" #include "lang/interpreter.h" #include "lang/parser.h" #include "lang/workspace.h" #include "log.h" #include "platform/path.h" static void interp_diagnostic(struct workspace *wk, uint32_t n_id, enum log_level lvl, const char *fmt, va_list args) { SBUF(buf); obj_vasprintf(wk, &buf, fmt, args); if (n_id) { struct node *n = get_node(wk->ast, n_id); error_message(wk->src, n->line, n->col, lvl, buf.buf); } else { log_print(true, lvl, "%s", buf.buf); } } void interp_error(struct workspace *wk, uint32_t n_id, const char *fmt, ...) { va_list args; va_start(args, fmt); interp_diagnostic(wk, n_id, log_error, fmt, args); va_end(args); } void interp_warning(struct workspace *wk, uint32_t n_id, const char *fmt, ...) { va_list args; va_start(args, fmt); interp_diagnostic(wk, n_id, log_warn, fmt, args); va_end(args); } bool bounds_adjust(struct workspace *wk, uint32_t len, int64_t *i) { if (*i < 0) { *i += len; } return *i < len; } bool boundscheck(struct workspace *wk, uint32_t n_id, uint32_t len, int64_t *i) { if (!bounds_adjust(wk, len, i)) { interp_error(wk, n_id, "index %" PRId64 " out of bounds", *i); return false; } return true; } bool rangecheck(struct workspace *wk, uint32_t n_id, int64_t min, int64_t max, int64_t n) { if (n < min || n > max) { interp_error(wk, n_id, "number %" PRId64 " out of bounds (%" PRId64 ", %" PRId64 ")", n, min, max); return false; } return true; } bool typecheck_simple_err(struct workspace *wk, obj o, type_tag type) { type_tag got = get_obj_type(wk, o); if (got != type) { LOG_E("expected type %s, got %s", obj_type_to_s(type), obj_type_to_s(got)); return false; } return true; } obj typechecking_type_to_arr(struct workspace *wk, type_tag t) { obj expected_types; make_obj(wk, &expected_types, obj_array); const char *single = NULL; if (!(t & obj_typechecking_type_tag)) { single = obj_type_to_s(t); } else if (t == tc_any) { single = "any"; } else if (t == obj_typechecking_type_tag) { single = "null"; } if (single) { obj_array_push(wk, expected_types, make_str(wk, single)); return expected_types; } uint32_t ot; for (ot = 1; ot <= tc_type_count; ++ot) { uint32_t tc = obj_type_to_tc_type(ot); if ((t & tc) != tc) { continue; } obj_array_push(wk, expected_types, make_str(wk, obj_type_to_s(ot))); } obj sorted; obj_array_sort(wk, NULL, expected_types, obj_array_sort_by_str, &sorted); return sorted; } const char * typechecking_type_to_s(struct workspace *wk, type_tag t) { obj typestr; obj_array_join(wk, false, typechecking_type_to_arr(wk, t), make_str(wk, "|"), &typestr); return get_cstr(wk, typestr); } static bool typecheck_typechecking_type(struct workspace *wk, uint32_t n_id, type_tag got, type_tag type, const char *fmt) { type |= tc_disabler; // always allow disabler type type_tag ot; for (ot = 1; ot <= tc_type_count; ++ot) { type_tag tc = obj_type_to_tc_type(ot); if ((type & tc) != tc) { continue; } if (ot == got) { return true; } } return false; } bool typecheck_custom(struct workspace *wk, uint32_t n_id, obj obj_id, type_tag type, const char *fmt) { type_tag got = get_obj_type(wk, obj_id); if (got == obj_typeinfo) { struct obj_typeinfo *ti = get_obj_typeinfo(wk, obj_id); type_tag got = ti->type; type_tag t = type; if (!(t & obj_typechecking_type_tag)) { t = obj_type_to_tc_type(type); } type_tag ot; for (ot = 1; ot <= tc_type_count; ++ot) { type_tag tc = obj_type_to_tc_type(ot); if ((got & tc) != tc) { continue; } if (typecheck_typechecking_type(wk, n_id, ot, t, fmt)) { return true; } } if (fmt) { interp_error(wk, n_id, fmt, typechecking_type_to_s(wk, t), typechecking_type_to_s(wk, got)); } return false; } else if ((type & obj_typechecking_type_tag)) { if (!typecheck_typechecking_type(wk, n_id, got, type, fmt)) { if (fmt) { interp_error(wk, n_id, fmt, typechecking_type_to_s(wk, type), obj_type_to_s(got)); } return false; } } else { if (got != type) { if (fmt) { interp_error(wk, n_id, fmt, obj_type_to_s(type), obj_type_to_s(got)); } return false; } } return true; } bool typecheck(struct workspace *wk, uint32_t n_id, obj obj_id, type_tag type) { return typecheck_custom(wk, n_id, obj_id, type, "expected type %s, got %s"); } struct typecheck_iter_ctx { uint32_t err_node; type_tag t; }; static enum iteration_result typecheck_array_iter(struct workspace *wk, void *_ctx, obj val) { struct typecheck_iter_ctx *ctx = _ctx; if (!typecheck_custom(wk, ctx->err_node, val, ctx->t, "expected type %s, got %s")) { return ir_err; } return ir_cont; } bool typecheck_array(struct workspace *wk, uint32_t n_id, obj arr, type_tag type) { if (!typecheck(wk, n_id, arr, obj_array)) { return false; } return obj_array_foreach(wk, arr, &(struct typecheck_iter_ctx) { .err_node = n_id, .t = type, }, typecheck_array_iter); } static enum iteration_result typecheck_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct typecheck_iter_ctx *ctx = _ctx; if (!typecheck_custom(wk, ctx->err_node, val, ctx->t, "expected type %s, got %s")) { return ir_err; } return ir_cont; } bool typecheck_dict(struct workspace *wk, uint32_t n_id, obj dict, type_tag type) { if (!typecheck(wk, n_id, dict, obj_dict)) { return false; } return obj_dict_foreach(wk, dict, &(struct typecheck_iter_ctx) { .err_node = n_id, .t = type, }, typecheck_dict_iter); } void assign_variable(struct workspace *wk, const char *name, obj o, uint32_t _n_id) { hash_set_str(¤t_project(wk)->scope, name, o); if (wk->dbg.watched && obj_array_in(wk, wk->dbg.watched, make_str(wk, name))) { LOG_I("watched variable \"%s\" changed", name); repl(wk, true); } } void unassign_variable(struct workspace *wk, const char *name) { hash_unset_str(¤t_project(wk)->scope, name); } static bool interp_chained(struct workspace *wk, uint32_t node_id, obj l_id, obj *res); static bool interp_method(struct workspace *wk, uint32_t node_id, obj l_id, obj *res) { obj tmp = 0; struct node *n = get_node(wk->ast, node_id); if (!builtin_run(wk, true, l_id, node_id, &tmp)) { return false; } if (n->chflg & node_child_d) { return interp_chained(wk, n->d, tmp, res); } else { *res = tmp; return true; } } bool interp_index(struct workspace *wk, struct node *n, obj l_id, bool do_chain, obj *res) { obj r_id; obj tmp = 0; if (!wk->interp_node(wk, n->r, &r_id)) { return false; } type_tag t = get_obj_type(wk, l_id); switch (t) { case obj_disabler: *res = disabler_id; return true; case obj_array: { if (!typecheck(wk, n->r, r_id, obj_number)) { return false; } int64_t i = get_obj_number(wk, r_id); if (!boundscheck(wk, n->r, get_obj_array(wk, l_id)->len, &i)) { return false; } obj_array_index(wk, l_id, i, &tmp); break; } case obj_dict: { if (!typecheck(wk, n->r, r_id, obj_string)) { return false; } if (!obj_dict_index(wk, l_id, r_id, &tmp)) { interp_error(wk, n->r, "key not in dictionary: %o", r_id); return false; } break; } case obj_custom_target: { if (!typecheck(wk, n->r, r_id, obj_number)) { return false; } int64_t i = get_obj_number(wk, r_id); struct obj_custom_target *tgt = get_obj_custom_target(wk, l_id); struct obj_array *arr = get_obj_array(wk, tgt->output); if (!boundscheck(wk, n->r, arr->len, &i)) { return false; } obj_array_index(wk, tgt->output, i, &tmp); break; } case obj_string: { if (!typecheck(wk, n->r, r_id, obj_number)) { return false; } int64_t i = get_obj_number(wk, r_id); const struct str *s = get_str(wk, l_id); if (!boundscheck(wk, n->r, s->len, &i)) { return false; } tmp = make_strn(wk, &s->s[i], 1); break; } default: interp_error(wk, n->r, "index unsupported for %s", obj_type_to_s(t)); return false; } if (do_chain && (n->chflg & node_child_d)) { return interp_chained(wk, n->d, tmp, res); } else { *res = tmp; return true; } } static bool interp_chained(struct workspace *wk, uint32_t node_id, obj l_id, obj *res) { struct node *n = get_node(wk->ast, node_id); switch (n->type) { case node_method: return interp_method(wk, node_id, l_id, res); case node_index: return interp_index(wk, n, l_id, true, res); default: assert(false && "unreachable"); break; } return false; } static bool interp_u_minus(struct workspace *wk, struct node *n, obj *res) { obj l_id; if (!wk->interp_node(wk, n->l, &l_id)) { return false; } else if (l_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->l, l_id, obj_number)) { return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, -get_obj_number(wk, l_id)); return true; } bool interp_arithmetic(struct workspace *wk, uint32_t err_node, enum arithmetic_type type, bool plusassign, uint32_t nl, uint32_t nr, obj *res) { obj l_id, r_id; if (!wk->interp_node(wk, nl, &l_id) || !wk->interp_node(wk, nr, &r_id)) { return false; } if (l_id == disabler_id || r_id == disabler_id) { *res = disabler_id; return true; } switch (get_obj_type(wk, l_id)) { case obj_string: { obj str; if (!typecheck_custom(wk, nr, r_id, obj_string, "unsupported operator for %s and %s")) { return false; } switch (type) { case arith_add: str = str_join(wk, l_id, r_id); break; case arith_div: { const struct str *ss1 = get_str(wk, l_id), *ss2 = get_str(wk, r_id); if (str_has_null(ss1)) { interp_error(wk, nl, "%o is an invalid path", l_id); return false; } if (str_has_null(ss2)) { interp_error(wk, nr, "%o is an invalid path", r_id); return false; } SBUF(buf); path_join(wk, &buf, ss1->s, ss2->s); str = sbuf_into_str(wk, &buf); break; } default: goto err1; } *res = str; break; } case obj_number: { int64_t num, l, r; if (!typecheck_custom(wk, nr, r_id, obj_number, "unsupported operator for %s and %s")) { return false; } l = get_obj_number(wk, l_id); r = get_obj_number(wk, r_id); switch (type) { case arith_add: num = l + r; break; case arith_div: if (!r) { interp_error(wk, nr, "divide by 0"); return false; } num = l / r; break; case arith_sub: num = l - r; break; case arith_mod: if (!r) { interp_error(wk, nr, "divide by 0"); return false; } num = l % r; break; case arith_mul: num = l * r; break; default: assert(false); return false; } make_obj(wk, res, obj_number); set_obj_number(wk, *res, num); break; } case obj_array: { switch (type) { case arith_add: if (plusassign) { *res = l_id; } else { obj_array_dup(wk, l_id, res); } if (get_obj_type(wk, r_id) == obj_array) { obj_array_extend(wk, *res, r_id); } else { obj_array_push(wk, *res, r_id); } return true; default: goto err1; } } case obj_dict: { if (!typecheck_custom(wk, nr, r_id, obj_dict, "unsupported operator for %s and %s")) { return false; } else if (type != arith_add) { goto err1; } if (plusassign) { obj_dict_merge_nodup(wk, l_id, r_id); *res = l_id; } else { obj_dict_merge(wk, l_id, r_id, res); } break; } default: goto err1; } return true; err1: assert(type < 5); interp_error(wk, err_node, "%s does not support %c", obj_type_to_s(get_obj_type(wk, l_id)), "+-%*/"[type]); return false; } static bool interp_assign(struct workspace *wk, struct node *n, obj *_) { obj rhs; if (!wk->interp_node(wk, n->r, &rhs)) { return false; } switch (get_obj_type(wk, rhs)) { case obj_environment: case obj_configuration_data: { obj cloned; if (!obj_clone(wk, wk, rhs, &cloned)) { return false; } rhs = cloned; break; } case obj_dict: { obj dup; obj_dict_dup(wk, rhs, &dup); rhs = dup; break; } case obj_array: { obj dup; obj_array_dup(wk, rhs, &dup); rhs = dup; } default: break; } if (!rhs) { interp_error(wk, n->l, "cannot assign variable to null"); return false; } wk->assign_variable(wk, get_node(wk->ast, n->l)->dat.s, rhs, 0); return true; } static bool interp_plusassign(struct workspace *wk, uint32_t n_id, obj *_) { struct node *n = get_node(wk->ast, n_id); obj rhs; if (!interp_arithmetic(wk, n_id, arith_add, true, n->l, n->r, &rhs)) { return false; } wk->assign_variable(wk, get_node(wk->ast, n->l)->dat.s, rhs, 0); return true; } static bool interp_array(struct workspace *wk, uint32_t n_id, obj *res) { obj l, r; struct node *n = get_node(wk->ast, n_id); n->chflg |= node_visited; if (n->type == node_empty) { make_obj(wk, res, obj_array); struct obj_array *arr = get_obj_array(wk, *res); arr->len = 0; arr->tail = *res; return true; } if (n->subtype == arg_kwarg) { interp_error(wk, n->l, "kwarg not valid in array constructor"); return false; } bool have_c = n->chflg & node_child_c && get_node(wk->ast, n->c)->type != node_empty; if (!wk->interp_node(wk, n->l, &l)) { return false; } if (have_c) { if (!interp_array(wk, n->c, &r)) { return false; } } make_obj(wk, res, obj_array); struct obj_array *arr = get_obj_array(wk, *res); arr->val = l; if ((arr->have_next = have_c)) { struct obj_array *arr_r = get_obj_array(wk, r); arr->len = arr_r->len + 1; arr->tail = arr_r->tail; arr->next = r; } else { arr->len = 1; arr->tail = *res; } return true; } static bool interp_dict(struct workspace *wk, uint32_t n_id, obj *res) { obj key, value, tail; struct node *n = get_node(wk->ast, n_id); n->chflg |= node_visited; if (n->type == node_empty) { make_obj(wk, res, obj_dict); struct obj_dict *dict = get_obj_dict(wk, *res); dict->len = 0; dict->tail = *res; return true; } assert(n->type == node_argument); if (n->subtype != arg_kwarg) { interp_error(wk, n->l, "non-kwarg not valid in dict constructor"); return false; } bool have_c = n->chflg & node_child_c && get_node(wk->ast, n->c)->type != node_empty; if (!wk->interp_node(wk, n->l, &key)) { return false; } if (!typecheck(wk, n->l, key, obj_string)) { return false; } if (!wk->interp_node(wk, n->r, &value)) { return false; } if (have_c) { if (!interp_dict(wk, n->c, &tail)) { return false; } } make_obj(wk, res, obj_dict); struct obj_dict *dict = get_obj_dict(wk, *res); dict->key = key; dict->val = value; if ((dict->have_next = have_c)) { struct obj_dict *dict_r = get_obj_dict(wk, tail); if (obj_dict_in(wk, tail, key)) { interp_error(wk, n->l, "key %o is duplicated", key); return false; } dict->len = dict_r->len + 1; dict->tail = dict_r->tail; dict->next = tail; } else { dict->len = 1; dict->tail = *res; } return true; } static bool interp_not(struct workspace *wk, struct node *n, obj *res) { obj obj_l_id; if (!wk->interp_node(wk, n->l, &obj_l_id)) { return false; } else if (obj_l_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->l, obj_l_id, obj_bool)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, !get_obj_bool(wk, obj_l_id)); return true; } static bool interp_andor(struct workspace *wk, struct node *n, obj *res) { obj obj_l_id, obj_r_id; if (!wk->interp_node(wk, n->l, &obj_l_id)) { return false; } else if (obj_l_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->l, obj_l_id, obj_bool)) { return false; } bool cond = get_obj_bool(wk, obj_l_id); if (n->type == node_and && !cond) { make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, false); return true; } else if (n->type == node_or && cond) { make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, true); return true; } if (!wk->interp_node(wk, n->r, &obj_r_id)) { return false; } else if (obj_r_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->r, obj_r_id, obj_bool)) { return false; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, get_obj_bool(wk, obj_r_id)); return true; } bool interp_comparison(struct workspace *wk, struct node *n, obj *res) { bool b; obj obj_l_id, obj_r_id; if (!wk->interp_node(wk, n->l, &obj_l_id)) { return false; } else if (!wk->interp_node(wk, n->r, &obj_r_id)) { return false; } if (obj_l_id == disabler_id || obj_r_id == disabler_id) { *res = disabler_id; return true; } switch ((enum comparison_type)n->subtype) { case comp_equal: b = obj_equal(wk, obj_l_id, obj_r_id); break; case comp_nequal: b = !obj_equal(wk, obj_l_id, obj_r_id); break; case comp_in: case comp_not_in: switch (get_obj_type(wk, obj_r_id)) { case obj_array: b = obj_array_in(wk, obj_r_id, obj_l_id); break; case obj_dict: if (!typecheck(wk, n->l, obj_l_id, obj_string)) { return false; } b = obj_dict_in(wk, obj_r_id, obj_l_id); break; default: interp_error(wk, n->r, "'in' not supported for %s", obj_type_to_s(get_obj_type(wk, obj_r_id))); return false; } if (n->subtype == comp_not_in) { b = !b; } break; case comp_lt: case comp_le: case comp_gt: case comp_ge: { if (!typecheck(wk, n->l, obj_l_id, obj_number) || !typecheck(wk, n->r, obj_r_id, obj_number)) { return false; } int64_t n_a = get_obj_number(wk, obj_l_id), n_b = get_obj_number(wk, obj_r_id); switch (n->subtype) { case comp_lt: b = n_a < n_b; break; case comp_le: b = n_a <= n_b; break; case comp_gt: b = n_a > n_b; break; case comp_ge: b = n_a >= n_b; break; default: UNREACHABLE; } break; } default: UNREACHABLE; } make_obj(wk, res, obj_bool); set_obj_bool(wk, *res, b); return true; } static bool interp_ternary(struct workspace *wk, struct node *n, obj *res) { obj cond_id; if (!wk->interp_node(wk, n->l, &cond_id)) { return false; } else if (cond_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->l, cond_id, obj_bool)) { return false; } uint32_t node = get_obj_bool(wk, cond_id) ? n->r : n->c; return wk->interp_node(wk, node, res); } static bool interp_if(struct workspace *wk, struct node *n, obj *res) { bool cond; switch ((enum if_type)n->subtype) { case if_if: case if_elseif: { obj cond_id; if (!wk->interp_node(wk, n->l, &cond_id)) { return false; } else if (cond_id == disabler_id) { *res = disabler_id; return true; } else if (!typecheck(wk, n->l, cond_id, obj_bool)) { return false; } cond = get_obj_bool(wk, cond_id); break; } case if_else: cond = true; break; default: assert(false); return false; } if (cond) { if (!wk->interp_node(wk, n->r, res)) { return false; } } else if (n->chflg & node_child_c) { if (!wk->interp_node(wk, n->c, res)) { return false; } } else { *res = 0; } return true; } struct interp_foreach_ctx { const char *id1, *id2; uint32_t n_l, n_r; uint32_t block_node; }; static enum iteration_result interp_foreach_common(struct workspace *wk, struct interp_foreach_ctx *ctx) { obj block_result; if (wk->dbg.stepping) { wk->dbg.last_line = 0; } if (get_node(wk->ast, ctx->block_node)->type == node_empty) { return ir_done; } if (!wk->interp_node(wk, ctx->block_node, &block_result)) { return ir_err; } switch (wk->loop_ctl) { case loop_continuing: wk->loop_ctl = loop_norm; break; case loop_breaking: wk->loop_ctl = loop_norm; return ir_done; case loop_norm: break; } return ir_cont; } static enum iteration_result interp_foreach_dict_iter(struct workspace *wk, void *_ctx, obj k_id, obj v_id) { struct interp_foreach_ctx *ctx = _ctx; wk->assign_variable(wk, ctx->id1, k_id, ctx->n_l); wk->assign_variable(wk, ctx->id2, v_id, ctx->n_r); return interp_foreach_common(wk, ctx); } static enum iteration_result interp_foreach_arr_iter(struct workspace *wk, void *_ctx, obj v_id) { struct interp_foreach_ctx *ctx = _ctx; wk->assign_variable(wk, ctx->id1, v_id, ctx->n_l); return interp_foreach_common(wk, ctx); } static bool interp_foreach(struct workspace *wk, struct node *n, obj *res) { obj iterable; bool ret; struct node *args = get_node(wk->ast, n->l); struct node *stmt_node = get_node(wk->ast, n->r); if (!(args->chflg & node_child_r) && stmt_node->type == node_function) { if (strcmp(get_node(wk->ast, stmt_node->l)->dat.s, "range") == 0 && !(stmt_node->chflg & node_child_d)) { struct range_params range_params; if (!func_range_common(wk, stmt_node->r, &range_params)) { return false; } struct interp_foreach_ctx ctx = { .id1 = get_node(wk->ast, args->l)->dat.s, .n_l = args->l, .block_node = n->c, }; ++wk->loop_depth; wk->loop_ctl = loop_norm; uint32_t i; bool break_out_of_loop = false; ret = true; for (i = range_params.start; i < range_params.stop; i += range_params.step) { obj num; make_obj(wk, &num, obj_number); set_obj_number(wk, num, i); switch (interp_foreach_arr_iter(wk, &ctx, num)) { case ir_err: ret = false; break_out_of_loop = true; break; case ir_cont: break; case ir_done: break_out_of_loop = true; break; } if (break_out_of_loop) { break; } } --wk->loop_depth; return ret; } } if (!wk->interp_node(wk, n->r, &iterable)) { return false; } switch (get_obj_type(wk, iterable)) { case obj_array: { if (args->chflg & node_child_r) { interp_error(wk, n->l, "array foreach needs exactly one variable to set"); return false; } struct interp_foreach_ctx ctx = { .id1 = get_node(wk->ast, args->l)->dat.s, .n_l = args->l, .block_node = n->c, }; ++wk->loop_depth; wk->loop_ctl = loop_norm; ret = obj_array_foreach(wk, iterable, &ctx, interp_foreach_arr_iter); --wk->loop_depth; break; } case obj_dict: { if (!(args->chflg & node_child_r)) { interp_error(wk, n->l, "dict foreach needs exactly two variables to set"); return false; } assert(get_node(wk->ast, get_node(wk->ast, args->r)->type == node_foreach_args)); struct interp_foreach_ctx ctx = { .id1 = get_node(wk->ast, args->l)->dat.s, .id2 = get_node(wk->ast, get_node(wk->ast, args->r)->l)->dat.s, .n_l = args->l, .n_r = get_node(wk->ast, args->r)->l, .block_node = n->c, }; ++wk->loop_depth; wk->loop_ctl = loop_norm; ret = obj_dict_foreach(wk, iterable, &ctx, interp_foreach_dict_iter); --wk->loop_depth; break; } default: interp_error(wk, n->r, "%s is not iterable", obj_type_to_s(get_obj_type(wk, iterable))); return false; } return ret; } static bool interp_func(struct workspace *wk, uint32_t n_id, obj *res) { obj tmp = 0; struct node *n = get_node(wk->ast, n_id); if (!builtin_run(wk, false, 0, n_id, &tmp)) { return false; } if (n->chflg & node_child_d) { return interp_chained(wk, n->d, tmp, res); } else { *res = tmp; return true; } } bool interp_stringify(struct workspace *wk, struct node *n, obj *res) { obj l_id; if (!wk->interp_node(wk, n->l, &l_id)) { return false; } if (!coerce_string(wk, n->l, l_id, res)) { return false; } return true; } bool interp_node(struct workspace *wk, uint32_t n_id, obj *res) { bool ret = false; *res = 0; struct node *n = get_node(wk->ast, n_id); n->chflg |= node_visited; // for analyzer /* L("%s", node_to_s(n)); */ if (wk->subdir_done) { return true; } if (wk->loop_ctl) { return true; } switch (n->type) { /* literals */ case node_bool: case node_string: case node_number: *res = n->l; ret = true; break; case node_array: ret = interp_array(wk, n->l, res); break; case node_dict: ret = interp_dict(wk, n->l, res); break; case node_id: if (!wk->get_variable(wk, n->dat.s, res, wk->cur_project)) { interp_error(wk, n_id, "undefined object"); ret = false; break; } ret = true; break; /* control flow */ case node_block: { bool have_r; interp_block: have_r = n->chflg & node_child_r && get_node(wk->ast, n->r)->type != node_empty; assert(n->type == node_block); obj obj_l; // this return value is disregarded // XXX: this is a hacky way to avoid messing up the debug node // for eval'd strings bool is_internal = strcmp(wk->src->label, "") == 0; bool was_stepping = wk->dbg.stepping; if (!is_internal && !wk->dbg.stepping) { wk->dbg.node = n->l; } if (!wk->interp_node(wk, n->l, &obj_l)) { if (wk->dbg.break_on_err) { repl(wk, true); } else { return false; } } if (!is_internal && was_stepping && wk->dbg.stepping && wk->dbg.last_line != get_node(wk->ast, n->l)->line) { wk->dbg.node = n->l; wk->dbg.last_line = get_node(wk->ast, n->l)->line; repl(wk, true); } if (have_r) { struct node *r = get_node(wk->ast, n->r); switch (r->type) { case node_empty: *res = obj_l; break; case node_block: n_id = n->r; n = r; goto interp_block; default: UNREACHABLE; } } else { *res = obj_l; } return true; } case node_if: ret = interp_if(wk, n, res); break; case node_foreach: ret = interp_foreach(wk, n, res); break; case node_continue: assert(wk->loop_depth && "continue outside loop"); wk->loop_ctl = loop_continuing; ret = true; break; case node_break: assert(wk->loop_depth && "break outside loop"); wk->loop_ctl = loop_breaking; ret = true; break; /* functions */ case node_function: ret = interp_func(wk, n_id, res); break; case node_method: case node_index: { obj l_id; assert(n->chflg & node_child_l); if (!wk->interp_node(wk, n->l, &l_id)) { ret = false; break; } ret = interp_chained(wk, n_id, l_id, res); break; } /* assignment */ case node_assignment: ret = interp_assign(wk, n, res); break; /* comparison stuff */ case node_not: ret = interp_not(wk, n, res); break; case node_and: case node_or: ret = interp_andor(wk, n, res); break; case node_comparison: ret = interp_comparison(wk, n, res); break; case node_ternary: ret = interp_ternary(wk, n, res); break; /* math */ case node_u_minus: ret = interp_u_minus(wk, n, res); break; case node_arithmetic: ret = interp_arithmetic(wk, n_id, n->subtype, false, n->l, n->r, res); break; case node_plusassign: ret = interp_plusassign(wk, n_id, res); break; /* special */ case node_stringify: ret = interp_stringify(wk, n, res); break; /* handled in other places */ case node_foreach_args: case node_argument: assert(false && "unreachable"); break; case node_empty: ret = true; break; /* never valid */ case node_paren: case node_empty_line: case node_null: assert(false && "invalid node"); break; } return ret; } void interpreter_init(void) { static bool init = false; if (init) { return; } build_func_impl_tables(); init = true; } muon-0.2.0/src/lang/lexer.c000066400000000000000000000450651441402162300154710ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include #include #include "buf_size.h" #include "error.h" #include "lang/eval.h" #include "lang/lexer.h" #include "log.h" #include "platform/filesystem.h" #include "platform/mem.h" #include "tracy.h" enum lex_result { lex_cont, lex_done, lex_fail, }; struct lexer { struct tokens *toks; struct source *source; struct source_data *sdata; const char *src; uint32_t i, data_i, line, line_start; struct { uint32_t paren, bracket, curl; } enclosing; enum lexer_mode mode; }; const char * tok_type_to_s(enum token_type type) { switch (type) { case tok_eof: return "end of file"; case tok_eol: return "end of line"; case tok_lparen: return "("; case tok_rparen: return ")"; case tok_lbrack: return "["; case tok_rbrack: return "]"; case tok_lcurl: return "{"; case tok_rcurl: return "}"; case tok_dot: return "."; case tok_comma: return ","; case tok_colon: return ":"; case tok_assign: return "="; case tok_plus: return "+"; case tok_minus: return "-"; case tok_star: return "*"; case tok_slash: return "/"; case tok_modulo: return "%"; case tok_plus_assign: return "+="; case tok_eq: return "=="; case tok_neq: return "!="; case tok_gt: return ">"; case tok_geq: return ">="; case tok_lt: return "<"; case tok_leq: return "<="; case tok_true: return "true"; case tok_false: return "false"; case tok_if: return "if"; case tok_else: return "else"; case tok_elif: return "elif"; case tok_endif: return "endif"; case tok_and: return "and"; case tok_or: return "or"; case tok_not: return "not"; case tok_foreach: return "foreach"; case tok_endforeach: return "endforeach"; case tok_in: return "in"; case tok_continue: return "continue"; case tok_break: return "break"; case tok_identifier: return "identifier"; case tok_string: return "string"; case tok_number: return "number"; case tok_question_mark: return "?"; case tok_stringify: return "stringify"; case tok_comment: return "comment"; case tok_fmt_eol: return "fmt_eol"; } assert(false && "unreachable"); return ""; } MUON_ATTR_FORMAT(printf, 2, 3) static void lex_error(struct lexer *l, const char *fmt, ...) { va_list args; va_start(args, fmt); struct token *last_tok = darr_get(&l->toks->tok, l->toks->tok.len - 1); error_messagev(l->source, last_tok->line, last_tok->col, log_error, fmt, args); va_end(args); } const char * tok_to_s(struct token *token) { static char buf[BUF_SIZE_S + 1]; uint32_t i; i = snprintf(buf, BUF_SIZE_S, "%s", tok_type_to_s(token->type)); if (token->n) { i += snprintf(&buf[i], BUF_SIZE_S - i, ":'%s'", token->dat.s); } else if (token->dat.n) { i += snprintf(&buf[i], BUF_SIZE_S - i, ":%" PRIi64, token->dat.n); } i += snprintf(&buf[i], BUF_SIZE_S - i, " line %d, col: %d", token->line, token->col); return buf; } static void advance(struct lexer *l) { if (l->i >= l->source->len) { return; } if (l->src[l->i] == '\n') { ++l->line; l->line_start = l->i + 1; } ++l->i; } static struct token * next_tok(struct lexer *l) { uint32_t idx = darr_push(&l->toks->tok, &(struct token){ 0 }); struct token *tok = darr_get(&l->toks->tok, idx); *tok = (struct token) { .line = l->line, .col = l->i - l->line_start + 1, }; return tok; } static bool is_valid_start_of_identifier(const char c) { return c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } static bool is_digit(const char c) { return '0' <= c && c <= '9'; } static bool is_hex_digit(const char c) { return is_digit(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); } static bool is_valid_inside_of_identifier(const char c) { return is_valid_start_of_identifier(c) || is_digit(c); } static bool is_skipchar(const char c) { return c == '\r' || c == ' ' || c == '\t' || c == '#'; } static void copy_into_sdata(struct lexer *lexer, struct token *tok, uint32_t start, uint32_t end) { tok->dat.s = &lexer->sdata->data[lexer->data_i]; tok->n = &lexer->src[end] - &lexer->src[start]; memcpy((char *)tok->dat.s, &lexer->src[start], tok->n); lexer->sdata->data[lexer->data_i + tok->n] = 0; lexer->data_i += tok->n + 1; } struct kw_table { const char *name; uint32_t val; }; static bool kw_lookup(const struct kw_table *table, const char *kw, uint32_t len, uint32_t *res) { uint32_t i; for (i = 0; table[i].name; ++i) { if (strlen(table[i].name) == len && strncmp(kw, table[i].name, len) == 0) { *res = table[i].val; return true; } } return false; } static bool keyword(struct lexer *lexer, const char *id, uint32_t len, enum token_type *res) { static const struct kw_table keywords[] = { { "and", tok_and }, { "break", tok_break }, { "continue", tok_continue }, { "elif", tok_elif }, { "else", tok_else }, { "endforeach", tok_endforeach }, { "endif", tok_endif }, { "false", tok_false }, { "foreach", tok_foreach }, { "if", tok_if }, { "in", tok_in }, { "not", tok_not }, { "or", tok_or }, { "true", tok_true }, { 0 }, }; if (kw_lookup(keywords, id, len, res)) { return true; } return false; } static enum lex_result number(struct lexer *lexer, struct token *tok) { tok->type = tok_number; uint32_t base = 10; uint32_t start = lexer->i; if (lexer->src[lexer->i] == '0') { switch (lexer->src[lexer->i + 1]) { case 'X': case 'x': base = 16; lexer->i += 2; break; case 'B': case 'b': base = 2; lexer->i += 2; break; case 'O': case 'o': base = 8; lexer->i += 2; break; default: advance(lexer); if (lexer->mode & lexer_mode_format) { copy_into_sdata(lexer, tok, start, lexer->i); } else { tok->dat.n = 0; } return lex_cont; } } char *endptr = NULL; errno = 0; int64_t val = strtol(&lexer->src[lexer->i], &endptr, base); assert(endptr); if (endptr == &lexer->src[lexer->i]) { lex_error(lexer, "invalid number"); return lex_fail; } if (errno == ERANGE) { if (val == LONG_MIN) { lex_error(lexer, "underflow when parsing number"); } else if (val == LONG_MAX) { lex_error(lexer, "overflow when parsing number"); } return lex_fail; } lexer->i += endptr - &lexer->src[lexer->i]; if (lexer->mode & lexer_mode_format) { copy_into_sdata(lexer, tok, start, lexer->i); } else { tok->dat.n = val; } return lex_cont; } static enum lex_result lex_identifier(struct lexer *lexer, struct token *token) { const char *start = &lexer->src[lexer->i]; uint32_t start_i = lexer->i; uint32_t len = 0; while (is_valid_inside_of_identifier(lexer->src[lexer->i])) { ++len; advance(lexer); } assert(len); if (!keyword(lexer, start, len, &token->type)) { token->type = tok_identifier; copy_into_sdata(lexer, token, start_i, start_i + len); } return lex_cont; } static bool write_utf8(struct lexer *l, struct token *tok, char *str, uint32_t val) { uint8_t pre, b, pre_len; uint32_t len, i; /* From: https://en.wikipedia.org/wiki/UTF-8#Encoding * U+0000 - U+007F 0x00 0xxxxxxx * U+0080 - U+07FF 0xc0 110xxxxx 10xxxxxx * U+0800 - U+FFFF 0xe0 1110xxxx 10xxxxxx 10xxxxxx * U+10000 - U+10FFFF 0xf0 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx * */ if (val <= 0x7f) { str[tok->n] = val; ++tok->n; return true; } else if (val <= 0x07ff) { len = 2; pre_len = 5; pre = 0xc0; b = 11; } else if (val <= 0xffff) { len = 3; pre_len = 4; pre = 0xe0; b = 16; } else if (val <= 0x10ffff) { len = 4; pre_len = 3; pre = 0xf0; b = 21; } else { lex_error(l, "invalid utf-8 escape 0x%x", val); return false; } str[tok->n] = pre | (val >> (b - pre_len)); ++tok->n; for (i = 1; i < len; ++i) { str[tok->n] = 0x80 | ((val >> (b - pre_len - (6 * i))) & 0x3f); ++tok->n; } return true; } static enum lex_result lex_string_char(struct lexer *lexer, struct token **tok, bool multiline, bool fstring, char **string, uint32_t *quotes) { bool done = false; struct token *token = *tok; char *str = *string; if (lexer->i >= lexer->source->len) { return lex_fail; } switch (lexer->src[lexer->i]) { case '\n': if (multiline) { str[token->n] = lexer->src[lexer->i]; ++token->n; } else { // unterminated string return lex_fail; } break; case '\\': { if (multiline) { str[token->n] = lexer->src[lexer->i]; ++token->n; } else { uint32_t esc_line = lexer->line; uint32_t esc_col = lexer->i - lexer->line_start + 1; switch (lexer->src[lexer->i + 1]) { case '\\': case '\'': advance(lexer); str[token->n] = lexer->src[lexer->i]; ++token->n; break; case 'a': advance(lexer); str[token->n] = '\a'; ++token->n; break; case 'b': advance(lexer); str[token->n] = '\b'; ++token->n; break; case 'f': advance(lexer); str[token->n] = '\f'; ++token->n; break; case 'r': advance(lexer); str[token->n] = '\r'; ++token->n; break; case 't': advance(lexer); str[token->n] = '\t'; ++token->n; break; case 'v': advance(lexer); str[token->n] = '\v'; ++token->n; break; case 'n': advance(lexer); str[token->n] = '\n'; ++token->n; break; case 'x': case 'u': case 'U': { uint32_t len = 0; switch (lexer->src[lexer->i + 1]) { case 'x': len = 2; break; case 'u': len = 4; break; case 'U': len = 8; break; } advance(lexer); char num[9] = { 0 }; uint32_t i; for (i = 0; i < len; ++i) { num[i] = lexer->src[lexer->i + 1]; if (!is_hex_digit(num[i])) { error_message(lexer->source, esc_line, esc_col, log_error, "unterminated hex escape"); return lex_fail; } advance(lexer); } uint32_t val = strtol(num, NULL, 16); if (!write_utf8(lexer, token, str, val)) { return lex_fail; } break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { char num[4] = { 0 }; uint32_t i; for (i = 0; i < 3; ++i) { num[i] = lexer->src[lexer->i + 1]; if (!is_digit(num[i])) { break; } advance(lexer); } assert(i); str[token->n] = strtol(num, NULL, 8); ++token->n; break; } default: str[token->n] = lexer->src[lexer->i]; ++token->n; advance(lexer); str[token->n] = lexer->src[lexer->i]; ++token->n; break; case 0: error_message(lexer->source, esc_line, esc_col, log_error, "unterminated hex escape"); return lex_fail; } } break; } case 0: // unterminated string return lex_fail; case '\'': if (multiline) { if (lexer->src[lexer->i + 1] == '\'' && lexer->src[lexer->i + 2] == '\'') { advance(lexer); advance(lexer); done = true; } else { str[token->n] = lexer->src[lexer->i]; ++token->n; } } else { done = true; } break; case '@': { if (!fstring) { str[token->n] = lexer->src[lexer->i]; ++token->n; break; } uint32_t i = 1; bool match = false; if (is_valid_start_of_identifier(lexer->src[lexer->i + i])) { for (; lexer->src[lexer->i + i]; ++i) { if (!is_valid_inside_of_identifier(lexer->src[lexer->i + i])) { break; } } if (lexer->src[lexer->i] == '@') { match = true; } } if (!match) { str[token->n] = lexer->src[lexer->i]; ++token->n; break; } advance(lexer); lexer->data_i += token->n + 1; next_tok(lexer)->type = tok_plus; next_tok(lexer)->type = tok_stringify; lex_identifier(lexer, next_tok(lexer)); advance(lexer); next_tok(lexer)->type = tok_plus; *tok = next_tok(lexer); *string = &lexer->sdata->data[lexer->data_i]; token = *tok; str = *string; token->type = tok_string; token->dat.s = str; return lex_cont; } default: str[token->n] = lexer->src[lexer->i]; ++token->n; break; } advance(lexer); if (done) { str[token->n] = 0; return lex_done; } return lex_cont; } static enum lex_result lex_string(struct lexer *lexer, struct token *token, bool fstring) { bool multiline; uint32_t quotes = 0; uint32_t start = lexer->i, data_start = lexer->data_i; if (fstring && (lexer->mode & lexer_mode_format)) { start = lexer->i - 1; fstring = false; } if (strncmp(&lexer->src[lexer->i], "'''", 3) == 0) { multiline = true; advance(lexer); advance(lexer); advance(lexer); } else { multiline = false; advance(lexer); } char *str = &lexer->sdata->data[lexer->data_i]; token->type = tok_string; token->dat.s = str; bool loop = true; enum lex_result ret = lex_cont; while (loop) { switch (lex_string_char(lexer, &token, multiline, fstring, &str, "es)) { case lex_cont: break; case lex_done: loop = false; break; case lex_fail: { bool terminated = false; while (lexer->i < lexer->source->len && lexer->src[lexer->i] && (multiline || (!multiline && lexer->src[lexer->i] != '\n'))) { if (lexer->src[lexer->i] == '\'') { ++quotes; if ((multiline && quotes == 3) || (!multiline && quotes)) { advance(lexer); terminated = true; break; } } advance(lexer); } if (!terminated) { lex_error(lexer, "unterminated string"); } loop = false; ret = lex_fail; break; } } } lexer->data_i += token->n + 1; if (lexer->mode & lexer_mode_format) { lexer->data_i = data_start; copy_into_sdata(lexer, token, start, lexer->i); } return ret; } static enum lex_result lexer_tokenize_one(struct lexer *lexer) { while (is_skipchar(lexer->src[lexer->i])) { if (lexer->src[lexer->i] == '#') { advance(lexer); uint32_t start = lexer->i; while (lexer->src[lexer->i] && lexer->src[lexer->i] != '\n') { advance(lexer); } if (lexer->mode & lexer_mode_format) { struct token *comment = next_tok(lexer); comment->type = tok_comment; copy_into_sdata(lexer, comment, start, lexer->i); } } else { advance(lexer); } } if (lexer->src[lexer->i] == '\\' && lexer->src[lexer->i + 1] == '\n') { advance(lexer); advance(lexer); return lexer_tokenize_one(lexer); } struct token *token = next_tok(lexer); if (lexer->src[lexer->i] == 'f' && lexer->src[lexer->i + 1] == '\'') { advance(lexer); return lex_string(lexer, token, true); } else if (lexer->src[lexer->i] == '\'') { return lex_string(lexer, token, false); } else if (is_valid_start_of_identifier(lexer->src[lexer->i])) { return lex_identifier(lexer, token); } else if (is_digit(lexer->src[lexer->i])) { return number(lexer, token); } else { switch (lexer->src[lexer->i]) { case '\n': if (lexer->enclosing.paren || lexer->enclosing.bracket || lexer->enclosing.curl) { if (lexer->mode & lexer_mode_format) { token->type = tok_fmt_eol; } else { goto skip; } } else { token->type = tok_eol; } break; case '(': ++lexer->enclosing.paren; token->type = tok_lparen; break; case ')': if (!lexer->enclosing.paren) { lex_error(lexer, "closing ')' without a matching opening '('"); return lex_fail; } --lexer->enclosing.paren; token->type = tok_rparen; break; case '[': ++lexer->enclosing.bracket; token->type = tok_lbrack; break; case ']': if (!lexer->enclosing.bracket) { lex_error(lexer, "closing ']' without a matching opening '['"); return lex_fail; } --lexer->enclosing.bracket; token->type = tok_rbrack; break; case '{': ++lexer->enclosing.curl; token->type = tok_lcurl; break; case '}': if (!lexer->enclosing.curl) { lex_error(lexer, "closing '}' without a matching opening '{'"); return lex_fail; } --lexer->enclosing.curl; token->type = tok_rcurl; break; case '.': token->type = tok_dot; break; case ',': token->type = tok_comma; break; case ':': token->type = tok_colon; break; case '?': token->type = tok_question_mark; break; // arithmetic case '+': if (lexer->src[lexer->i + 1] == '=') { advance(lexer); token->type = tok_plus_assign; } else { token->type = tok_plus; } break; case '-': token->type = tok_minus; break; case '*': token->type = tok_star; break; case '/': token->type = tok_slash; break; case '%': token->type = tok_modulo; break; case '=': if (lexer->src[lexer->i + 1] == '=') { advance(lexer); token->type = tok_eq; } else { token->type = tok_assign; } break; case '!': if (lexer->src[lexer->i + 1] == '=') { advance(lexer); token->type = tok_neq; } else { lex_error(lexer, "unexpected character: '%c'", lexer->src[lexer->i]); return lex_fail; } break; case '>': if (lexer->src[lexer->i + 1] == '=') { advance(lexer); token->type = tok_geq; } else { token->type = tok_gt; } break; case '<': if (lexer->src[lexer->i + 1] == '=') { advance(lexer); token->type = tok_leq; } else { token->type = tok_lt; } break; case '\0': if (lexer->i != lexer->source->len) { lex_error(lexer, "unexpected null byte"); return lex_fail; } token->type = tok_eof; return lex_done; default: lex_error(lexer, "unexpected character: '%c'", lexer->src[lexer->i]); return lex_fail; } advance(lexer); return lex_cont; } assert(false && "unreachable"); return lex_fail; skip: advance(lexer); --lexer->toks->tok.len; return lex_cont; } static bool tokenize(struct lexer *lexer) { bool success = true, loop = true; while (loop && lexer->i <= lexer->source->len) { switch (lexer_tokenize_one(lexer)) { case lex_cont: break; case lex_fail: success = false; advance(lexer); break; case lex_done: loop = false; break; } } if (success) { assert(((struct token *)darr_get(&lexer->toks->tok, lexer->toks->tok.len - 1))->type == tok_eof && "lexer failed to terminate token stream with tok_eof"); } /* { */ /* uint32_t i; */ /* struct token *tok; */ /* for (i = 0; i < lexer->toks->tok.len; ++i) { */ /* tok = darr_get(&lexer->toks->tok, i); */ /* log_plain("%s\n", tok_to_s(tok)); */ /* } */ /* } */ return success; } bool lexer_lex(struct tokens *toks, struct source_data *sdata, struct source *src, enum lexer_mode mode) { TracyCZoneAutoS; *toks = (struct tokens) { 0 }; struct lexer lexer = { .source = src, .line = 1, .src = src->src, .toks = toks, .sdata = sdata, .mode = mode, }; darr_init(&toks->tok, 2048, sizeof(struct token)); // TODO: this could be done much more conservatively sdata->data_len = src->len + 1; sdata->data = z_malloc(sdata->data_len); bool ret = tokenize(&lexer); TracyCZoneAutoE; return ret; } void tokens_destroy(struct tokens *toks) { darr_destroy(&toks->tok); } muon-0.2.0/src/lang/object.c000066400000000000000000001171401441402162300156120ustar00rootroot00000000000000/* * SPDX-FileCopyrightText: Stone Tickle * SPDX-FileCopyrightText: Simon Zeni * SPDX-FileCopyrightText: illiliti * SPDX-License-Identifier: GPL-3.0-only */ #include "compat.h" #include #include #include #include #include "buf_size.h" #include "error.h" #include "lang/interpreter.h" #include "lang/object.h" #include "lang/parser.h" #include "log.h" #include "options.h" #include "platform/mem.h" #include "tracy.h" type_tag obj_type_to_tc_type(enum obj_type t) { if (!t) { return obj_typechecking_type_tag; } assert(t - 1 < tc_type_count); return (((type_tag)1) << (t - 1)) | obj_typechecking_type_tag; } static void * get_obj_internal(struct workspace *wk, obj id, enum obj_type type) { struct obj_internal *o = bucket_array_get(&wk->objs, id); if (o->t != type) { LOG_E("internal type error, expected %s but got %s", obj_type_to_s(type), obj_type_to_s(o->t)); abort(); return NULL; } switch (type) { case obj_bool: case obj_file: case obj_feature_opt: return &o->val; break; case obj_number: case obj_string: case obj_array: case obj_dict: case obj_compiler: case obj_build_target: case obj_custom_target: case obj_subproject: case obj_dependency: case obj_external_program: case obj_run_result: case obj_configuration_data: case obj_test: case obj_module: case obj_install_target: case obj_environment: case obj_include_directory: case obj_option: case obj_generator: case obj_generated_list: case obj_alias_target: case obj_both_libs: case obj_typeinfo: case obj_source_set: case obj_source_configuration: return bucket_array_get(&wk->obj_aos[o->t - _obj_aos_start], o->val); case obj_null: case obj_meson: case obj_disabler: case obj_machine: LOG_E("tried to get singleton object of type %s", obj_type_to_s(type)); abort(); default: assert(false && "tried to get invalid object type"); return NULL; } } enum obj_type get_obj_type(struct workspace *wk, obj id) { struct obj_internal *o = bucket_array_get(&wk->objs, id); return o->t; } bool get_obj_bool(struct workspace *wk, obj o) { return *(bool *)get_obj_internal(wk, o, obj_bool); } int64_t get_obj_number(struct workspace *wk, obj o) { return *(int64_t *)get_obj_internal(wk, o, obj_number); } void set_obj_bool(struct workspace *wk, obj o, bool v) { *(bool *)get_obj_internal(wk, o, obj_bool) = v; } void set_obj_number(struct workspace *wk, obj o, int64_t v) { *(int64_t *)get_obj_internal(wk, o, obj_number) = v; } obj * get_obj_file(struct workspace *wk, obj o) { return get_obj_internal(wk, o, obj_file); } const char * get_file_path(struct workspace *wk, obj o) { return get_cstr(wk, *get_obj_file(wk, o)); } const struct str * get_str(struct workspace *wk, obj s) { return get_obj_internal(wk, s, obj_string); } enum feature_opt_state get_obj_feature_opt(struct workspace *wk, obj fo) { enum feature_opt_state state; state = *(enum feature_opt_state *)get_obj_internal(wk, fo, obj_feature_opt); if (state == feature_opt_auto) { obj auto_features_opt; // NOTE: wk->global_opts won't be initialized if we are loading // a serial dump if (wk->global_opts && get_option(wk, NULL, &WKSTR("auto_features"), &auto_features_opt)) { struct obj_option *opt = get_obj_option(wk, auto_features_opt); return *(enum feature_opt_state *)get_obj_internal(wk, opt->val, obj_feature_opt); } else { return state; } } else { return state; } } void set_obj_feature_opt(struct workspace *wk, obj fo, enum feature_opt_state state) { *(enum feature_opt_state *)get_obj_internal(wk, fo, obj_feature_opt) = state; } #define OBJ_GETTER(type) \ struct type * \ get_ ## type(struct workspace *wk, obj o) \ { \ return get_obj_internal(wk, o, type); \ } OBJ_GETTER(obj_array) OBJ_GETTER(obj_dict) OBJ_GETTER(obj_compiler) OBJ_GETTER(obj_build_target) OBJ_GETTER(obj_custom_target) OBJ_GETTER(obj_subproject) OBJ_GETTER(obj_dependency) OBJ_GETTER(obj_external_program) OBJ_GETTER(obj_run_result) OBJ_GETTER(obj_configuration_data) OBJ_GETTER(obj_test) OBJ_GETTER(obj_module) OBJ_GETTER(obj_install_target) OBJ_GETTER(obj_environment) OBJ_GETTER(obj_include_directory) OBJ_GETTER(obj_option) OBJ_GETTER(obj_generator) OBJ_GETTER(obj_generated_list) OBJ_GETTER(obj_alias_target) OBJ_GETTER(obj_both_libs) OBJ_GETTER(obj_typeinfo) OBJ_GETTER(obj_source_set) OBJ_GETTER(obj_source_configuration) #undef OBJ_GETTER void make_obj(struct workspace *wk, obj *id, enum obj_type type) { uint32_t val; *id = wk->objs.len; switch (type) { case obj_bool: case obj_file: case obj_null: case obj_meson: case obj_disabler: case obj_machine: case obj_feature_opt: val = 0; break; case obj_number: case obj_string: case obj_array: case obj_dict: case obj_compiler: case obj_build_target: case obj_custom_target: case obj_subproject: case obj_dependency: case obj_external_program: case obj_run_result: case obj_configuration_data: case obj_test: case obj_module: case obj_install_target: case obj_environment: case obj_include_directory: case obj_option: case obj_generator: case obj_generated_list: case obj_alias_target: case obj_both_libs: case obj_source_set: case obj_source_configuration: case obj_typeinfo: { struct bucket_array *ba = &wk->obj_aos[type - _obj_aos_start]; val = ba->len; bucket_array_pushn(ba, NULL, 0, 1); break; } default: assert(false && "tried to make invalid object type"); } bucket_array_push(&wk->objs, &(struct obj_internal){ .t = type, .val = val }); #ifdef TRACY_ENABLE if (wk->tracy.is_master_workspace) { uint64_t mem = 0; mem += bucket_array_size(&wk->objs); uint32_t i; for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { mem += bucket_array_size(&wk->obj_aos[i]); } #define MB(b) ((double)b / 1048576.0) TracyCPlot("objects", wk->objs.len); TracyCPlot("object memory (mb)", MB(mem)); TracyCPlot("string memory (mb)", MB(bucket_array_size(&wk->chrs))); #undef MB } #endif } void obj_set_clear_mark(struct workspace *wk, struct obj_clear_mark *mk) { mk->obji = wk->objs.len; bucket_array_save(&wk->chrs, &mk->chrs); bucket_array_save(&wk->objs, &mk->objs); uint32_t i; for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { bucket_array_save(&wk->obj_aos[i], &mk->obj_aos[i]); } } void obj_clear(struct workspace *wk, const struct obj_clear_mark *mk) { struct obj_internal *o; struct str *ss; uint32_t i; for (i = mk->obji; i < wk->objs.len; ++i) { o = bucket_array_get(&wk->objs, i); if (o->t == obj_string) { ss = bucket_array_get( &wk->obj_aos[obj_string - _obj_aos_start], o->val); if (ss->flags & str_flag_big) { z_free((void *)ss->s); } } } bucket_array_restore(&wk->objs, &mk->objs); bucket_array_restore(&wk->chrs, &mk->chrs); for (i = 0; i < obj_type_count - _obj_aos_start; ++i) { bucket_array_restore(&wk->obj_aos[i], &mk->obj_aos[i]); } } const char * obj_type_to_s(enum obj_type t) { switch (t) { case obj_null: return "void"; case obj_compiler: return "compiler"; case obj_dependency: return "dep"; case obj_meson: return "meson"; case obj_string: return "str"; case obj_number: return "int"; case obj_array: return "list"; case obj_dict: return "dict"; case obj_bool: return "bool"; case obj_file: return "file"; case obj_build_target: return "build_tgt"; case obj_subproject: return "subproject"; case obj_machine: return "build_machine"; case obj_feature_opt: return "feature"; case obj_external_program: return "external_program"; case obj_run_result: return "runresult"; case obj_configuration_data: return "cfg_data"; case obj_custom_target: return "custom_tgt"; case obj_test: return "test"; case obj_module: return "module"; case obj_install_target: return "install_tgt"; case obj_environment: return "env"; case obj_include_directory: return "inc"; case obj_option: return "option"; case obj_disabler: return "disabler"; case obj_generator: return "generator"; case obj_generated_list: return "generated_list"; case obj_alias_target: return "alias_tgt"; case obj_both_libs: return "both_libs"; case obj_typeinfo: return "typeinfo"; case obj_source_set: return "source_set"; case obj_source_configuration: return "source_configuration"; case obj_type_count: assert(false); return "uh oh"; } assert(false && "unreachable"); return NULL; } struct obj_equal_iter_ctx { obj other_container; uint32_t i; }; static enum iteration_result obj_equal_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_equal_iter_ctx *ctx = _ctx; obj r; obj_array_index(wk, ctx->other_container, ctx->i, &r); if (!obj_equal(wk, val, r)) { return ir_err; } ++ctx->i; return ir_cont; } static enum iteration_result obj_equal_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct obj_equal_iter_ctx *ctx = _ctx; obj r; if (!obj_dict_index(wk, ctx->other_container, key, &r)) { return ir_err; } else if (!obj_equal(wk, val, r)) { return ir_err; } return ir_cont; } bool obj_equal(struct workspace *wk, obj left, obj right) { if (left == right) { return true; } enum obj_type t = get_obj_type(wk, left); if (t != get_obj_type(wk, right)) { return false; } switch (t) { case obj_string: return str_eql(get_str(wk, left), get_str(wk, right)); case obj_file: return str_eql(get_str(wk, *get_obj_file(wk, left)), get_str(wk, *get_obj_file(wk, right))); case obj_number: return get_obj_number(wk, left) == get_obj_number(wk, right); case obj_bool: return get_obj_bool(wk, left) == get_obj_bool(wk, right); case obj_array: { struct obj_equal_iter_ctx ctx = { .other_container = right, }; struct obj_array *l = get_obj_array(wk, left), *r = get_obj_array(wk, right); return l->len == r->len && obj_array_foreach(wk, left, &ctx, obj_equal_array_iter); } case obj_feature_opt: { return get_obj_feature_opt(wk, left) == get_obj_feature_opt(wk, right); } case obj_include_directory: { struct obj_include_directory *l, *r; l = get_obj_include_directory(wk, left); r = get_obj_include_directory(wk, right); return l->is_system == r->is_system && obj_equal(wk, l->path, r->path); } case obj_dict: { struct obj_equal_iter_ctx ctx = { .other_container = right, }; struct obj_dict *l = get_obj_dict(wk, left), *r = get_obj_dict(wk, right); return l->len == r->len && obj_dict_foreach(wk, left, &ctx, obj_equal_dict_iter); } default: /* LOG_W("TODO: compare %s", obj_type_to_s(t)); */ return false; } } /* * arrays */ bool obj_array_foreach(struct workspace *wk, obj arr, void *ctx, obj_array_iterator cb) { struct obj_array *a = get_obj_array(wk, arr); if (!a->len) { return true; } while (true) { switch (cb(wk, ctx, a->val)) { case ir_cont: break; case ir_done: return true; case ir_err: return false; } if (!a->have_next) { break; } a = get_obj_array(wk, a->next); } return true; } struct obj_array_foreach_flat_ctx { void *usr_ctx; obj_array_iterator cb; }; static enum iteration_result obj_array_foreach_flat_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_array_foreach_flat_ctx *ctx = _ctx; if (get_obj_type(wk, val) == obj_array) { if (!obj_array_foreach(wk, val, ctx, obj_array_foreach_flat_iter)) { return ir_err; } else { return ir_cont; } } else if (get_obj_type(wk, val) == obj_typeinfo && get_obj_typeinfo(wk, val)->type == tc_array) { // skip typeinfo arrays as they wouldn't be yielded if they // were real arrays return ir_cont; } else { return ctx->cb(wk, ctx->usr_ctx, val); } return ir_cont; } bool obj_array_foreach_flat(struct workspace *wk, obj arr, void *usr_ctx, obj_array_iterator cb) { struct obj_array_foreach_flat_ctx ctx = { .usr_ctx = usr_ctx, .cb = cb, }; return obj_array_foreach(wk, arr, &ctx, obj_array_foreach_flat_iter); } void obj_array_push(struct workspace *wk, obj arr, obj child) { obj child_arr; struct obj_array *a, *tail, *c; if (!(a = get_obj_array(wk, arr))->len) { a->tail = arr; a->len = 1; a->val = child; a->have_next = false; return; } make_obj(wk, &child_arr, obj_array); c = get_obj_array(wk, child_arr); c->val = child; a = get_obj_array(wk, arr); tail = get_obj_array(wk, a->tail); assert(!tail->have_next); tail->have_next = true; tail->next = child_arr; a->tail = child_arr; ++a->len; } void obj_array_prepend(struct workspace *wk, obj *arr, obj val) { obj prepend; make_obj(wk, &prepend, obj_array); obj_array_push(wk, prepend, val); obj_array_extend_nodup(wk, prepend, *arr); *arr = prepend; } struct obj_array_index_of_iter_ctx { obj l; bool res; uint32_t i; }; static enum iteration_result obj_array_index_of_iter(struct workspace *wk, void *_ctx, obj v) { struct obj_array_index_of_iter_ctx *ctx = _ctx; if (obj_equal(wk, ctx->l, v)) { ctx->res = true; return ir_done; } ++ctx->i; return ir_cont; } bool obj_array_index_of(struct workspace *wk, obj arr, obj val, uint32_t *idx) { struct obj_array_index_of_iter_ctx ctx = { .l = val }; obj_array_foreach(wk, arr, &ctx, obj_array_index_of_iter); *idx = ctx.i; return ctx.res; } bool obj_array_in(struct workspace *wk, obj arr, obj val) { uint32_t _; return obj_array_index_of(wk, arr, val, &_); } struct obj_array_index_iter_ctx { obj res, i, tgt; }; static enum iteration_result obj_array_index_iter(struct workspace *wk, void *_ctx, obj v) { struct obj_array_index_iter_ctx *ctx = _ctx; if (ctx->i == ctx->tgt) { ctx->res = v; return ir_done; } ++ctx->i; return ir_cont; } void obj_array_index(struct workspace *wk, obj arr, int64_t i, obj *res) { struct obj_array_index_iter_ctx ctx = { .tgt = i }; assert(i >= 0 && i < get_obj_array(wk, arr)->len); obj_array_foreach(wk, arr, &ctx, obj_array_index_iter); *res = ctx.res; } struct obj_array_dup_ctx { obj *arr; }; static enum iteration_result obj_array_dup_iter(struct workspace *wk, void *_ctx, obj v) { struct obj_array_dup_ctx *ctx = _ctx; obj_array_push(wk, *ctx->arr, v); return ir_cont; } void obj_array_dup(struct workspace *wk, obj arr, obj *res) { struct obj_array_dup_ctx ctx = { .arr = res }; make_obj(wk, res, obj_array); obj_array_foreach(wk, arr, &ctx, obj_array_dup_iter); } void obj_array_extend_nodup(struct workspace *wk, obj arr, obj arr2) { struct obj_array *a, *b, *tail; if (!(b = get_obj_array(wk, arr2))->len) { return; } if (!(a = get_obj_array(wk, arr))->len) { struct obj_array_dup_ctx ctx = { .arr = &arr }; obj_array_foreach(wk, arr2, &ctx, obj_array_dup_iter); return; } tail = get_obj_array(wk, a->tail); assert(!tail->have_next); tail->have_next = true; tail->next = arr2; a->tail = b->tail; a->len += b->len; } void obj_array_extend(struct workspace *wk, obj arr, obj arr2) { obj dup; obj_array_dup(wk, arr2, &dup); obj_array_extend_nodup(wk, arr, dup); } struct obj_array_join_ctx { obj *res; const struct str *join; uint32_t i, len; }; static enum iteration_result obj_array_join_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_array_join_ctx *ctx = _ctx; if (!typecheck_simple_err(wk, val, obj_string)) { return ir_err; } const struct str *ss = get_str(wk, val); str_appn(wk, *ctx->res, ss->s, ss->len); if (ctx->i < ctx->len - 1) { str_appn(wk, *ctx->res, ctx->join->s, ctx->join->len); } ++ctx->i; return ir_cont; } static enum iteration_result obj_array_flat_len_iter(struct workspace *wk, void *_ctx, obj _) { uint32_t *len = _ctx; ++(*len); return ir_cont; } static uint32_t obj_array_flat_len(struct workspace *wk, obj arr) { uint32_t len = 0; obj_array_foreach_flat(wk, arr, &len, obj_array_flat_len_iter); return len; } bool obj_array_join(struct workspace *wk, bool flat, obj arr, obj join, obj *res) { *res = make_str(wk, ""); if (!typecheck_simple_err(wk, join, obj_string)) { return false; } struct obj_array_join_ctx ctx = { .join = get_str(wk, join), .res = res, }; if (flat) { ctx.len = obj_array_flat_len(wk, arr); return obj_array_foreach_flat(wk, arr, &ctx, obj_array_join_iter); } else { ctx.len = get_obj_array(wk, arr)->len; return obj_array_foreach(wk, arr, &ctx, obj_array_join_iter); } } void obj_array_tail(struct workspace *wk, obj arr, obj *res) { const struct obj_array *a = get_obj_array(wk, arr); if (a->have_next) { struct obj_array *n = get_obj_array(wk, a->next); n->tail = a->tail; n->len = a->len; *res = a->next; } else { // the tail of a zero or single element array is an empty array make_obj(wk, res, obj_array); } } void obj_array_set(struct workspace *wk, obj arr, int64_t i, obj v) { assert(i >= 0 && i < get_obj_array(wk, arr)->len); uint32_t j = 0; while (true) { if (j == i) { get_obj_array(wk, arr)->val = v; return; } assert(get_obj_array(wk, arr)->have_next); arr = get_obj_array(wk, arr)->next; ++j; } assert(false && "unreachable"); } void obj_array_del(struct workspace *wk, obj arr, int64_t i) { uint32_t j = 0; obj p = arr; struct obj_array *head = get_obj_array(wk, arr), *prev, *next, *del; assert(i >= 0 && i < head->len); if (i == 0) { if (head->have_next) { next = get_obj_array(wk, head->next); next->len = head->len - 1; next->tail = head->tail; *head = *next; } else { *head = (struct obj_array) { 0 }; } return; } while (true) { if (j == i) { del = get_obj_array(wk, arr); break; } p = arr; assert(get_obj_array(wk, arr)->have_next); arr = get_obj_array(wk, arr)->next; ++j; } prev = get_obj_array(wk, p); if (del->have_next) { prev->next = del->next; } else { prev->have_next = false; } --head->len; } static enum iteration_result obj_array_dedup_iter(struct workspace *wk, void *_ctx, obj val) { if (hash_get(&wk->obj_hash, &val)) { return ir_cont; } hash_set(&wk->obj_hash, &val, true); obj *res = _ctx; if (!obj_array_in(wk, *res, val)) { obj_array_push(wk, *res, val); } return ir_cont; } void obj_array_dedup(struct workspace *wk, obj arr, obj *res) { hash_clear(&wk->obj_hash); make_obj(wk, res, obj_array); obj_array_foreach(wk, arr, res, obj_array_dedup_iter); } bool obj_array_flatten_one(struct workspace *wk, obj val, obj *res) { enum obj_type t = get_obj_type(wk, val); if (t == obj_array) { struct obj_array *v = get_obj_array(wk, val); if (v->len == 1) { obj_array_index(wk, val, 0, res); } else { return false; } } else { *res = val; } return true; } int32_t obj_array_sort_by_str(struct workspace *wk, void *_ctx, obj a, obj b) { const struct str *sa = get_str(wk, a), *sb = get_str(wk, b); uint32_t min = sa->len > sb->len ? sb->len : sa->len; return memcmp(sa->s, sb->s, min); } static enum iteration_result obj_array_sort_push_to_da_iter(struct workspace *wk, void *_ctx, obj v) { struct darr *da = _ctx; darr_push(da, &v); return ir_cont; } struct obj_array_sort_ctx { struct workspace *wk; void *usr_ctx; obj_array_sort_func func; }; static int32_t obj_array_sort_wrapper(const void *a, const void *b, void *_ctx) { struct obj_array_sort_ctx *ctx = _ctx; return ctx->func(ctx->wk, ctx->usr_ctx, *(obj *)a, *(obj *)b); } void obj_array_sort(struct workspace *wk, void *usr_ctx, obj arr, obj_array_sort_func func, obj *res) { uint32_t len = get_obj_array(wk, arr)->len; if (!len) { *res = arr; return; } struct darr da; darr_init(&da, len, sizeof(obj)); obj_array_foreach(wk, arr, &da, obj_array_sort_push_to_da_iter); struct obj_array_sort_ctx ctx = { .wk = wk, .usr_ctx = usr_ctx, .func = func, }; darr_sort(&da, &ctx, obj_array_sort_wrapper); make_obj(wk, res, obj_array); uint32_t i; for (i = 0; i < da.len; ++i) { obj_array_push(wk, *res, *(obj *)darr_get(&da, i)); } darr_destroy(&da); } /* * dictionaries */ bool obj_dict_foreach(struct workspace *wk, obj dict, void *ctx, obj_dict_iterator cb) { if (!get_obj_dict(wk, dict)->len) { return true; } while (true) { switch (cb(wk, ctx, get_obj_dict(wk, dict)->key, get_obj_dict(wk, dict)->val)) { case ir_cont: break; case ir_done: return true; case ir_err: return false; } if (!get_obj_dict(wk, dict)->have_next) { break; } dict = get_obj_dict(wk, dict)->next; } return true; } struct obj_dict_dup_ctx { obj *dict; }; static enum iteration_result obj_dict_dup_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct obj_dict_dup_ctx *ctx = _ctx; obj_dict_set(wk, *ctx->dict, key, val); return ir_cont; } void obj_dict_dup(struct workspace *wk, obj dict, obj *res) { struct obj_dict_dup_ctx ctx = { .dict = res }; make_obj(wk, res, obj_dict); obj_dict_foreach(wk, dict, &ctx, obj_dict_dup_iter); } static enum iteration_result obj_dict_merge_iter(struct workspace *wk, void *_ctx, obj key, obj val) { obj *res = _ctx; obj_dict_set(wk, *res, key, val); return ir_cont; } void obj_dict_merge_nodup(struct workspace *wk, obj dict, obj dict2) { obj_dict_foreach(wk, dict2, &dict, obj_dict_merge_iter); } void obj_dict_merge(struct workspace *wk, obj dict, obj dict2, obj *res) { obj_dict_dup(wk, dict, res); obj_dict_merge_nodup(wk, *res, dict2); } union obj_dict_key_comparison_key { struct str string; uint32_t num; }; /* other is marked uint32_t since it can be used to represent an obj or a number */ typedef bool ((*obj_dict_key_comparison_func)(struct workspace *wk, union obj_dict_key_comparison_key *key, uint32_t other)); static bool obj_dict_key_comparison_func_string(struct workspace *wk, union obj_dict_key_comparison_key *key, obj other) { const struct str *ss_a = get_str(wk, other); return str_eql(ss_a, &key->string); } static bool obj_dict_key_comparison_func_objstr(struct workspace *wk, union obj_dict_key_comparison_key *key, obj other) { return str_eql(get_str(wk, key->num), get_str(wk, other)); } static bool obj_dict_key_comparison_func_int(struct workspace *wk, union obj_dict_key_comparison_key *key, uint32_t other) { return key->num == other; } static bool _obj_dict_index(struct workspace *wk, obj dict, union obj_dict_key_comparison_key *key, obj_dict_key_comparison_func comp, obj **res) { if (!get_obj_dict(wk, dict)->len) { return false; } while (true) { if (comp(wk, key, get_obj_dict(wk, dict)->key)) { *res = &get_obj_dict(wk, dict)->val; return true; } if (!get_obj_dict(wk, dict)->have_next) { break; } dict = get_obj_dict(wk, dict)->next; } return false; } bool obj_dict_index_strn(struct workspace *wk, obj dict, const char *str, uint32_t len, obj *res) { obj *r; union obj_dict_key_comparison_key key = { .string = { .s = str, .len = len, } }; if (!_obj_dict_index(wk, dict, &key, obj_dict_key_comparison_func_string, &r)) { return false; } *res = *r; return true; } bool obj_dict_index(struct workspace *wk, obj dict, obj key, obj *res) { const struct str *k = get_str(wk, key); return obj_dict_index_strn(wk, dict, k->s, k->len, res); } bool obj_dict_in(struct workspace *wk, obj dict, obj key) { obj res; return obj_dict_index(wk, dict, key, &res); } static void _obj_dict_set(struct workspace *wk, obj dict, obj_dict_key_comparison_func comp, obj key, obj val) { struct obj_dict *d; //, *tail; obj tail; /* empty dict */ if (!(d = get_obj_dict(wk, dict))->len) { d->key = key; d->val = val; d->tail = dict; ++d->len; return; } obj *r; union obj_dict_key_comparison_key k = { .num = key }; if (_obj_dict_index(wk, dict, &k, comp, &r)) { *r = val; return; } /* set new value */ make_obj(wk, &tail, obj_dict); d = get_obj_dict(wk, tail); d->key = key; d->val = val; d = get_obj_dict(wk, get_obj_dict(wk, dict)->tail); assert(!d->have_next); d->have_next = true; d->next = tail; d = get_obj_dict(wk, dict); d->tail = tail; ++d->len; } void obj_dict_set(struct workspace *wk, obj dict, obj key, obj val) { _obj_dict_set(wk, dict, obj_dict_key_comparison_func_objstr, key, val); } /* dict convienence functions */ void obj_dict_seti(struct workspace *wk, obj dict, uint32_t key, obj val) { _obj_dict_set(wk, dict, obj_dict_key_comparison_func_int, key, val); } bool obj_dict_geti(struct workspace *wk, obj dict, uint32_t key, obj *val) { obj *r; if (_obj_dict_index(wk, dict, &(union obj_dict_key_comparison_key){ .num = key }, obj_dict_key_comparison_func_int, &r)) { *val = *r; return true; } return false; } /* */ struct obj_iterable_foreach_ctx { void *ctx; obj_dict_iterator cb; }; static enum iteration_result obj_iterable_foreach_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_iterable_foreach_ctx *ctx = _ctx; return ctx->cb(wk, ctx->ctx, val, 0); } bool obj_iterable_foreach(struct workspace *wk, obj dict_or_array, void *ctx, obj_dict_iterator cb) { switch (get_obj_type(wk, dict_or_array)) { case obj_dict: { return obj_dict_foreach(wk, dict_or_array, ctx, cb); } case obj_array: { return obj_array_foreach(wk, dict_or_array, &(struct obj_iterable_foreach_ctx){ .ctx = ctx, .cb = cb, }, obj_iterable_foreach_array_iter); } default: UNREACHABLE_RETURN; } } /* */ struct obj_clone_ctx { struct workspace *wk_dest; obj container; }; static enum iteration_result obj_clone_array_iter(struct workspace *wk_src, void *_ctx, obj val) { struct obj_clone_ctx *ctx = _ctx; obj dest_val; if (!obj_clone(wk_src, ctx->wk_dest, val, &dest_val)) { return ir_err; } obj_array_push(ctx->wk_dest, ctx->container, dest_val); return ir_cont; } static enum iteration_result obj_clone_dict_iter(struct workspace *wk_src, void *_ctx, obj key, obj val) { struct obj_clone_ctx *ctx = _ctx; obj dest_key, dest_val; if (!obj_clone(wk_src, ctx->wk_dest, key, &dest_key)) { return ir_err; } else if (!obj_clone(wk_src, ctx->wk_dest, val, &dest_val)) { return ir_err; } obj_dict_set(ctx->wk_dest, ctx->container, dest_key, dest_val); return ir_cont; } bool obj_clone(struct workspace *wk_src, struct workspace *wk_dest, obj val, obj *ret) { if (val >= wk_src->objs.len) { LOG_E("invalid object"); return false; } enum obj_type t = get_obj_type(wk_src, val); /* L("cloning %s", obj_type_to_s(t)); */ switch (t) { case obj_null: *ret = 0; return true; case obj_number: make_obj(wk_dest, ret, t); set_obj_number(wk_dest, *ret, get_obj_number(wk_src, val)); return true; case obj_bool: make_obj(wk_dest, ret, t); set_obj_bool(wk_dest, *ret, get_obj_bool(wk_src, val)); return true; case obj_string: { *ret = str_clone(wk_src, wk_dest, val); return true; } case obj_file: make_obj(wk_dest, ret, t); *get_obj_file(wk_dest, *ret) = str_clone(wk_src, wk_dest, *get_obj_file(wk_src, val)); return true; case obj_array: make_obj(wk_dest, ret, t); return obj_array_foreach(wk_src, val, &(struct obj_clone_ctx) { .container = *ret, .wk_dest = wk_dest }, obj_clone_array_iter); case obj_dict: make_obj(wk_dest, ret, t); return obj_dict_foreach(wk_src, val, &(struct obj_clone_ctx) { .container = *ret, .wk_dest = wk_dest }, obj_clone_dict_iter); case obj_test: { make_obj(wk_dest, ret, t); struct obj_test *test = get_obj_test(wk_src, val), *o = get_obj_test(wk_dest, *ret); *o = *test; o->name = str_clone(wk_src, wk_dest, test->name); o->exe = str_clone(wk_src, wk_dest, test->exe); if (test->workdir) { o->workdir = str_clone(wk_src, wk_dest, test->workdir); } if (!obj_clone(wk_src, wk_dest, test->args, &o->args)) { return false; } if (!obj_clone(wk_src, wk_dest, test->env, &o->env)) { return false; } if (!obj_clone(wk_src, wk_dest, test->suites, &o->suites)) { return false; } if (!obj_clone(wk_src, wk_dest, test->depends, &o->depends)) { return false; } if (!obj_clone(wk_src, wk_dest, test->timeout, &o->timeout)) { return false; } if (!obj_clone(wk_src, wk_dest, test->priority, &o->priority)) { return false; } return true; } case obj_install_target: { make_obj(wk_dest, ret, t); struct obj_install_target *in = get_obj_install_target(wk_src, val), *o = get_obj_install_target(wk_dest, *ret); o->src = str_clone(wk_src, wk_dest, in->src); o->dest = str_clone(wk_src, wk_dest, in->dest); o->build_target = in->build_target; o->type = in->type; o->has_perm = in->has_perm; o->perm = in->perm; if (!obj_clone(wk_src, wk_dest, in->exclude_directories, &o->exclude_directories)) { return false; } if (!obj_clone(wk_src, wk_dest, in->exclude_files, &o->exclude_files)) { return false; } return true; } case obj_environment: { make_obj(wk_dest, ret, obj_environment); struct obj_environment *env = get_obj_environment(wk_src, val), *o = get_obj_environment(wk_dest, *ret); if (!obj_clone(wk_src, wk_dest, env->actions, &o->actions)) { return false; } return true; } case obj_option: { make_obj(wk_dest, ret, t); struct obj_option *opt = get_obj_option(wk_src, val), *o = get_obj_option(wk_dest, *ret); o->source = opt->source; o->type = opt->type; o->builtin = opt->builtin; o->yield = opt->yield; if (!obj_clone(wk_src, wk_dest, opt->name, &o->name)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->val, &o->val)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->choices, &o->choices)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->max, &o->max)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->min, &o->min)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->deprecated, &o->deprecated)) { return false; } if (!obj_clone(wk_src, wk_dest, opt->description, &o->description)) { return false; } return true; } case obj_feature_opt: { make_obj(wk_dest, ret, t); set_obj_feature_opt(wk_dest, *ret, get_obj_feature_opt(wk_src, val)); return true; } case obj_configuration_data: { make_obj(wk_dest, ret, t); struct obj_configuration_data *conf = get_obj_configuration_data(wk_src, val), *o = get_obj_configuration_data(wk_dest, *ret); if (!obj_clone(wk_src, wk_dest, conf->dict, &o->dict)) { return false; } return true; } default: LOG_E("unable to clone '%s'", obj_type_to_s(t)); return false; } } struct obj_to_s_ctx { struct sbuf *sb; uint32_t cont_i, cont_len; }; static enum iteration_result obj_to_s_array_iter(struct workspace *wk, void *_ctx, obj val) { struct obj_to_s_ctx *ctx = _ctx; obj_to_s(wk, val, ctx->sb); if (ctx->cont_i < ctx->cont_len - 1) { sbuf_pushs(wk, ctx->sb, ", "); } ++ctx->cont_i; return ir_cont; } static enum iteration_result obj_to_s_dict_iter(struct workspace *wk, void *_ctx, obj key, obj val) { struct obj_to_s_ctx *ctx = _ctx; obj_to_s(wk, key, ctx->sb); sbuf_pushs(wk, ctx->sb, ": "); obj_to_s(wk, val, ctx->sb); if (ctx->cont_i < ctx->cont_len - 1) { sbuf_pushs(wk, ctx->sb, ", "); } ++ctx->cont_i; return ir_cont; } static void obj_to_s_str(struct workspace *wk, struct obj_to_s_ctx *ctx, obj s) { sbuf_push(wk, ctx->sb, '\''); str_unescape(wk, ctx->sb, get_str(wk, s), true); sbuf_push(wk, ctx->sb, '\''); } void obj_to_s(struct workspace *wk, obj o, struct sbuf *sb) { struct obj_to_s_ctx ctx = { .sb = sb }; enum obj_type t = get_obj_type(wk, o); switch (t) { case obj_include_directory: { struct obj_include_directory *inc = get_obj_include_directory(wk, o); sbuf_pushs(wk, sb, "path); sbuf_pushs(wk, sb, ">"); break; } case obj_dependency: { struct obj_dependency *dep = get_obj_dependency(wk, o); sbuf_pushs(wk, sb, "name) { obj_to_s_str(wk, &ctx, dep->name); } sbuf_pushf(wk, sb, " | found: %s, pkgconf: %s>", dep->flags & dep_flag_found ? "yes" : "no", dep->type == dependency_type_pkgconf ? "yes" : "no" ); break; } case obj_alias_target: sbuf_pushs(wk, sb, "name); sbuf_pushs(wk, sb, ">"); break; case obj_build_target: { struct obj_build_target *tgt = get_obj_build_target(wk, o); const char *type = NULL; switch (tgt->type) { case tgt_executable: type = "executable"; break; case tgt_static_library: type = "static_library"; break; case tgt_dynamic_library: type = "shared_library"; break; case tgt_shared_module: type = "shared_module"; break; } sbuf_pushf(wk, sb, "<%s ", type); obj_to_s_str(wk, &ctx, tgt->name); sbuf_pushs(wk, sb, ">"); break; } case obj_feature_opt: switch (get_obj_feature_opt(wk, o)) { case feature_opt_auto: sbuf_pushs(wk, sb, "'auto'"); break; case feature_opt_enabled: sbuf_pushs(wk, sb, "'enabled'"); break; case feature_opt_disabled: sbuf_pushs(wk, sb, "'disabled'"); break; } break; case obj_test: { struct obj_test *test = get_obj_test(wk, o); sbuf_pushs(wk, sb, "test("); obj_to_s_str(wk, &ctx, test->name); sbuf_pushs(wk, sb, ", "); obj_to_s_str(wk, &ctx, test->exe); if (test->args) { sbuf_pushs(wk, sb, ", args: "); obj_to_s(wk, test->args, sb); } if (test->should_fail) { sbuf_pushs(wk, sb, ", should_fail: true"); } sbuf_pushs(wk, sb, ")"); break; } case obj_file: sbuf_pushs(wk, sb, ""); break; case obj_string: { obj_to_s_str(wk, &ctx, o); break; } case obj_number: sbuf_pushf(wk, sb, "%" PRId64, get_obj_number(wk, o)); break; case obj_bool: sbuf_pushs(wk, sb, get_obj_bool(wk, o) ? "true" : "false"); break; case obj_array: ctx.cont_len = get_obj_array(wk, o)->len; sbuf_pushs(wk, sb, "["); obj_array_foreach(wk, o, &ctx, obj_to_s_array_iter); sbuf_pushs(wk, sb, "]"); break; case obj_dict: ctx.cont_len = get_obj_dict(wk, o)->len; sbuf_pushs(wk, sb, "{"); obj_dict_foreach(wk, o, &ctx, obj_to_s_dict_iter); sbuf_pushs(wk, sb, "}"); break; case obj_external_program: { struct obj_external_program *prog = get_obj_external_program(wk, o); sbuf_pushf(wk, sb, "<%s found: %s", obj_type_to_s(t), prog->found ? "true" : "false"); if (prog->found) { sbuf_pushs(wk, sb, ", path: "); obj_to_s_str(wk, &ctx, prog->full_path); } sbuf_pushs(wk, sb, ">"); break; } case obj_option: { struct obj_option *opt = get_obj_option(wk, o); sbuf_pushs(wk, sb, "