├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── adv_diff ├── .gitignore ├── dub.json └── source │ └── redub │ └── libs │ └── adv_diff │ └── files.d ├── colorize ├── .gitignore ├── dub.json └── source │ └── redub │ └── libs │ └── colorize │ ├── colors.d │ ├── cwrite.d │ ├── package.d │ └── winterm.d ├── d_dependencies ├── .gitignore ├── dub.json ├── simports │ ├── dub.deps │ └── hip_deps └── source │ └── d_depedencies.d ├── dub.json ├── dub.selections.json ├── dub_sdl_to_json ├── .gitignore ├── dub.json ├── dub.selections.json └── source │ └── dub_sdl_to_json.d ├── hipjson ├── .gitignore ├── dub.json └── source │ └── hipjson.d ├── package_suppliers ├── .gitignore ├── dub.json └── source │ └── redub │ └── libs │ └── package_suppliers │ ├── dub_registry.d │ └── utils.d ├── plugins └── getmodules │ ├── .gitignore │ ├── dub.json │ └── source │ └── getmodules.d ├── redub.code-workspace ├── replace_redub.bat ├── replace_redub.sh ├── semver ├── .gitignore ├── dub.json └── source │ └── redub │ └── libs │ ├── semver.d │ └── version_parser.d ├── source ├── app.d └── redub │ ├── api.d │ ├── buildapi.d │ ├── building │ ├── cache.d │ ├── compile.d │ └── utils.d │ ├── cli │ └── dub.d │ ├── command_generators │ ├── automatic.d │ ├── commons.d │ ├── d_compilers.d │ ├── dmd.d │ ├── gnu_based.d │ ├── ldc.d │ └── linkers.d │ ├── compiler_identification.d │ ├── logging.d │ ├── meta.d │ ├── misc │ ├── console_control_handler.d │ ├── file_size_format.d │ ├── find_executable.d │ ├── github_tag_check.d │ ├── glob_entries.d │ ├── hard_link.d │ ├── ldc_conf_parser.d │ ├── mini_regex.d │ └── path.d │ ├── package_searching │ ├── api.d │ ├── cache.d │ ├── downloader.d │ ├── dub.d │ └── entry.d │ ├── parsers │ ├── automatic.d │ ├── base.d │ ├── build_type.d │ ├── environment.d │ ├── json.d │ ├── sdl.d │ └── single.d │ ├── plugin │ ├── api.d │ ├── build.d │ └── load.d │ └── tree_generators │ └── dub.d └── tests ├── deps ├── .gitignore ├── a │ ├── .gitignore │ ├── dub.json │ └── source │ │ └── a.d ├── b │ ├── .gitignore │ ├── dub.json │ └── source │ │ └── b.d ├── dub.json ├── src │ └── app.d └── x │ ├── .gitignore │ ├── dub.json │ └── source │ └── common.d ├── gcc ├── dub.json └── source │ └── gcc.c ├── gxx ├── dub.json └── source │ └── gcc.cc ├── multi_lang ├── .gitignore ├── c_dep │ ├── dub.json │ └── source │ │ ├── another_dep.c │ │ └── some_dep.c ├── dub.json ├── dub.selections.json └── source │ └── app.d ├── platforms ├── .gitignore ├── dub.json ├── extra │ ├── linux │ │ └── platform.d │ └── osx │ │ └── platform.d └── source │ └── app.d ├── plugin_test ├── .gitignore ├── dub.json └── source │ ├── app.d │ └── imports.txt ├── preb ├── .gitignore ├── dub.json ├── other │ └── test.d └── source │ └── app.d ├── redub_lib ├── .gitignore ├── dub.json ├── dub.selections.json └── source │ └── app.d ├── sLib ├── .gitignore ├── dub.json ├── source │ └── app.d └── special │ ├── dub.json │ └── source │ └── application.d ├── subcfg ├── .gitignore ├── dub.json ├── source │ └── app.d └── test │ └── app2.d ├── tarsd ├── .gitignore ├── dub.json ├── dub.selections.json └── source │ └── app.d ├── vec-bench ├── .gitignore ├── dub.json ├── dub.selections.json └── source │ └── main.d └── vibehello ├── .gitignore ├── dub.json ├── dub.selections.json └── source └── app.d /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Artifacts 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | include: 14 | - os: ubuntu-latest 15 | arch: x86_64 16 | - os: ubuntu-24.04-arm 17 | arch: arm64 18 | - os: macos-latest 19 | arch: arm64 20 | - os: windows-latest 21 | arch: x86_64 22 | fail-fast: false 23 | runs-on: ${{matrix.os}} 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: dlang-community/setup-dlang@v2 27 | with: 28 | compiler: ldc-latest 29 | - name: Build 30 | run: | 31 | dub build -b release-debug 32 | 33 | - name: Rename artifact on Windows 34 | if: runner.os == 'Windows' 35 | run: | 36 | dir 37 | move build/redub.exe redub-${{ matrix.os }}-${{ matrix.arch }}.exe 38 | - name: Upload artifacts on Windows 39 | uses: actions/upload-artifact@v4 40 | if: runner.os == 'Windows' 41 | with: 42 | name: redub-windows-latest-x86_64.exe 43 | path: redub-windows-latest-x86_64.exe 44 | 45 | - name: Rename artifact Unix 46 | if: runner.os != 'Windows' 47 | run: | 48 | ls -R 49 | mv build/redub redub-${{ matrix.os }}-${{ matrix.arch }} 50 | - name: Upload artifacts on Unix 51 | uses: actions/upload-artifact@v4 52 | if: runner.os != 'Windows' && github.ref == 'refs/heads/main' 53 | with: 54 | name: redub-${{ matrix.os }}-${{ matrix.arch }} 55 | path: redub-${{ matrix.os }}-${{ matrix.arch }} 56 | 57 | # freebsd: 58 | # strategy: 59 | # matrix: 60 | # arch: [x86_64] 61 | # fail-fast: false 62 | # runs-on: ubuntu-latest 63 | # steps: 64 | # - uses: actions/checkout@v4 65 | # - name: Run FreeBSD VM 66 | # uses: vmactions/freebsd-vm@v1 67 | # with: 68 | # usesh: true 69 | # prepare: | 70 | # pkg install -y dub ldc 71 | # run: | 72 | # dub build -b release 73 | # - uses: actions/upload-artifact@v4 74 | # if: github.ref == 'refs/heads/main' 75 | # with: 76 | # name: redub-freebsd-14.2-x86_64 77 | # path: build 78 | 79 | alpine: 80 | strategy: 81 | matrix: 82 | arch: [x86_64] 83 | fail-fast: false 84 | runs-on: ubuntu-latest 85 | container: 86 | image: alpine:latest 87 | defaults: 88 | run: 89 | shell: sh 90 | steps: 91 | - uses: actions/checkout@v4 92 | 93 | - name: Prepare 94 | run: | 95 | apk update 96 | apk add --no-cache ldc dub clang 97 | - name: Build 98 | run: | 99 | dub build -b release-debug 100 | - run: mv build/redub redub-alpine-x86_64 101 | - uses: actions/upload-artifact@v4 102 | if: github.ref == 'refs/heads/main' 103 | with: 104 | name: redub-alpine-x86_64 105 | path: redub-alpine-x86_64 106 | 107 | update-release: 108 | runs-on: ubuntu-latest 109 | needs: [build, alpine] 110 | permissions: 111 | contents: write 112 | steps: 113 | - name: "Download build artifacts" 114 | uses: actions/download-artifact@v4.1.8 115 | with: 116 | merge-multiple: true 117 | # - uses: actions/checkout@v4 118 | # - name: Fetch all tags 119 | # run: git fetch --tags 120 | # - name: "Get latest tag" 121 | # run: echo "TAG=$(git describe --tags --abbrev=0) >> $GITHUB_ENV" 122 | 123 | - name: Display structure of downloaded files 124 | run: ls -R 125 | 126 | - name: "Update Prebuilt Binaries" 127 | uses: ncipollo/release-action@v1 128 | with: 129 | artifacts: "redub-ubuntu-latest-x86_64,redub-ubuntu-24.04-arm-arm64,redub-macos-latest-arm64,redub-windows-latest-x86_64.exe,redub-alpine-x86_64" 130 | allowUpdates: "true" 131 | tag: "Build" 132 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .dub 3 | d_dependencies/simports/ 4 | .history 5 | *.exe 6 | .vs 7 | *.a 8 | *.lib 9 | *.o 10 | *.obj 11 | bin 12 | .DS_Store 13 | *.out 14 | .ldc2_cache 15 | trace.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Marcelo Silva Nascimento Mancini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redub - Dub Based Build System 2 | 3 | 4 | ## Running redub without having it on path 5 | - Change directory to the project you want to build and enter in terminal `dub run redub` 6 | - You may also get help by running `dub run redub -- --help` 7 | - If you're unsure on how to update redub to the latest version using dub, you may also do `dub run redub -- update` 8 | 9 | ## Building redub 10 | - Enter in terminal and execute `dub` 11 | - Highly recommended that you build it with `dub build -b release-debug --compiler=ldc2` since this will also improve its speed on dependency resolution 12 | - I would also add redub/bin to the environment variables, with that, you'll be able to simply execute `redub` in the folder you're in and get your project built and running 13 | - After having your first redub version, you may also update redub by entering on terminal `redub update`. This will download the latest version, rebuild redub with optimizations and replace your current redub executable 14 | 15 | ## Using its library API 16 | 17 | The usage of the library APIispretty straightforward. You get mainly 2 functions 18 | 1. `resolveDependencies` which will parse the project and its dependencies, after that, you got all the project information 19 | 2. `buildProject` which will get the project information and build in parallel 20 | 21 | ```d 22 | import redub.api; 23 | import redub.logging; 24 | 25 | void main() 26 | { 27 | import std.file; 28 | //Enables logging on redub 29 | setLogLevel(LogLevel.verbose); 30 | 31 | //Gets the project information 32 | ProjectDetails d = resolveDependencies( 33 | invalidateCache: false, 34 | std.system.os, 35 | CompilationDetails("dmd", "arch not yet implemented", "dmd v[2.105.0]"), 36 | ProjectToParse("configuration", getcwd(), "subPackage", "path/to/dub/recipe.json (optional)") 37 | ); 38 | 39 | /** Optionally, you can change some project information by accessing the details.tree (a ProjectNode), from there, you can freely modify the BuildRequirements of the project 40 | * d.tree.requirements.cfg.outputDirectory = "some/path"; 41 | * d.tree.requirements.cfg.dFlags~= "-gc"; 42 | */ 43 | 44 | //Execute the build process 45 | buildProject(d); 46 | } 47 | ``` 48 | 49 | ## Redub Plugins 50 | 51 | Redub has now new additions, starting from v1.14.0. Those are called **plugins**. 52 | For using it, on the JSON, you **must** specify first the plugins that are usable. For doing that, you need to add: 53 | 54 | ```json 55 | "plugins": { 56 | "getmodules": "C:\\Users\\Hipreme\\redub\\plugins\\getmodules" 57 | } 58 | ``` 59 | 60 | That line will both build that project and load it inside the registered plugins (That means the same name can't be specified twice) 61 | 62 | The path may be either a .d module or a dub project 63 | > WARNING: This may be a subject of change and may also only support redub projects in the future, since that may only complicate code with a really low gain 64 | 65 | Redub will start distributing some build plugins in the future. Currently, getmodules plugins is inside this repo as an example only but may be better used. 66 | Only preBuild is currently supported since I haven't found situations yet to other cases. 67 | For it to be considered a redub plugin to be built, that is the minimal code: 68 | 69 | ```d 70 | module getmodules; 71 | import redub.plugin.api; 72 | 73 | class GetModulePlugin : RedubPlugin {} 74 | mixin PluginEntrypoint!(GetModulePlugin); 75 | ``` 76 | 77 | For using it on prebuild, you simply specify the module and its arguments: 78 | ```json 79 | "preBuildPlugins": { 80 | "getmodules": ["source/imports.txt"] 81 | } 82 | ``` 83 | 84 | ### Useful links regarding plugins: 85 | - [**GetModule plugin**](./plugins/getmodules/source/getmodules.d) 86 | - [**Example Usage**](./tests/plugin_test/dub.json) 87 | 88 | ## Multi language 89 | 90 | Redub has also an experimental support for building and linking C/C++ code together with D code. For that, you need to define a dub.json: 91 | ```json 92 | { 93 | "language": "C" 94 | } 95 | ``` 96 | 97 | With that, you'll be able to specify that your dependency is a C/C++ dependency. then, you'll be able to build it by calling `redub --cc=gcc`. You can also 98 | specify both D and C at the same time `redub --cc=gcc --dc=dmd`. Which will use DMD to build D and GCC to C. 99 | 100 | You can see that in the example project: [**Multi Language Redub Project**](./tests/multi_lang/dub.json) 101 | 102 | 103 | # Project Meta 104 | 105 | 106 | ## Making it faster 107 | Have you ever wondered why [dub](https://github.com/dlang/dub) was slow? I tried solving it, but its codebase was fairly unreadable. After building this project, I've implemented features that dub don't use 108 | 109 | - Lazy build project configuration evaluation 110 | - Parallelization on build sorted by dependency 111 | - Faster JSON parsing 112 | - Fully parallelized build when only link is waiting for dependencies 113 | 114 | ### Philosophy 115 | 116 | - Separate build system from package manager. 117 | - Have total backward compatibility on dub for initial versions. 118 | - On initial versions, develop using phobos only 119 | - Make it less stateful. 120 | - Achieve at least 90% of what dub does. 121 | - Isolate each process. This will make easier for adding and future contributors 122 | 123 | ## Achieving it 124 | 125 | ### Legend 126 | - api -> Can be freely be imported from any module 127 | - module -> Needs to be isolated as much as possible from each module. If one needs to communicate with other, a bridge/api may be created after the initial idea 128 | 129 | ### How it works 130 | Here are described the modules which do most of the work if someone wants to contribute. 131 | 132 | - buildapi: Defines the contents of build configurations, tree of projects and commons for them 133 | - parsers.json: Parse dub.json into a build configuration 134 | - parsers.automatic: Parse using an automatic parser identification 135 | - cli.dub + app: Parse CLI to get the build root and an initial build configuration 136 | - parsers.environment: Merge environment variables into build configuration 137 | - tree_generators.dub: Output build dependency tree while merging their configurations 138 | - command_generator.automatic: Convert build configuration into compilation flags 139 | - buildapi + building.compile: Transform build tree into dependency order tree 140 | - building.compile: Spawn build processes for the dependencies until it links 141 | 142 | 143 | ### Contributor Attracting 144 | - Isolate module as much as possible to attract contributors working on self contained modules which only gets input and returns an output 145 | 146 | ### Starting small 147 | - No need to handle edge cases in the beginning. They may become even separate modules. 148 | 149 | ### A week project 150 | - This project had a small start. I gave one week for doing it, but since it was very succesful on its 151 | achievements, I decided to extend a little the deadline for achieving support. 152 | Right now, it has been tested with 153 | 154 | ### Working examples 155 | Those projects were fairly tested while building this one 156 | - dub 157 | - glui 158 | - dplug 159 | - arsd-official 160 | - Hipreme Engine 161 | -------------------------------------------------------------------------------- /adv_diff/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /adv_diff 6 | adv_diff.so 7 | adv_diff.dylib 8 | adv_diff.dll 9 | adv_diff.a 10 | adv_diff.lib 11 | adv_diff-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /adv_diff/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "dependencies": { 6 | "hipjson": {"path": "../hipjson"} 7 | }, 8 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 9 | "description": "An advanced diff checker. Used for files and paths.", 10 | "license": "proprietary", 11 | "name": "adv_diff" 12 | } -------------------------------------------------------------------------------- /colorize/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /colorize 6 | colorize.so 7 | colorize.dylib 8 | colorize.dll 9 | colorize.a 10 | colorize.lib 11 | colorize-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /colorize/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Hipreme" 4 | ], 5 | "copyright": "Copyright © 2024, Hipreme", 6 | "description": "A minimal D application.", 7 | "license": "proprietary", 8 | "name": "colorize" 9 | } -------------------------------------------------------------------------------- /colorize/source/redub/libs/colorize/colors.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Authors: Pedro Tacla Yamada 3 | * Date: June 9, 2014 4 | * License: Licensed under the MIT license. See LICENSE for more information 5 | * Version: 1.0.2 6 | */ 7 | module redub.libs.colorize.colors; 8 | 9 | private template color_type(int offset) 10 | { 11 | enum type : int 12 | { 13 | init = 39 + offset, 14 | 15 | black = 30 + offset, 16 | red = 31 + offset, 17 | green = 32 + offset, 18 | yellow = 33 + offset, 19 | blue = 34 + offset, 20 | magenta = 35 + offset, 21 | cyan = 36 + offset, 22 | white = 37 + offset, 23 | 24 | light_black = 90 + offset, 25 | light_red = 91 + offset, 26 | light_green = 92 + offset, 27 | light_yellow = 93 + offset, 28 | light_blue = 94 + offset, 29 | light_magenta = 95 + offset, 30 | light_cyan = 96 + offset, 31 | light_white = 97 + offset 32 | } 33 | } 34 | 35 | alias color_type!0 .type fg; 36 | alias color_type!10 .type bg; 37 | 38 | // Text modes 39 | enum mode : int 40 | { 41 | init = 0, 42 | bold = 1, 43 | underline = 4, 44 | blink = 5, 45 | swap = 7, 46 | hide = 8 47 | } 48 | 49 | /** 50 | * Wraps a string around color escape sequences. 51 | * 52 | * Params: 53 | * str = The string to wrap with colors and modes 54 | * c = The foreground color (see the fg enum type) 55 | * b = The background color (see the bg enum type) 56 | * m = The text mode (see the mode enum type) 57 | * Example: 58 | * --- 59 | * writeln("This is blue".color(fg.blue)); 60 | * writeln( 61 | * color("This is red over green blinking", fg.blue, bg.green, mode.blink) 62 | * ); 63 | * --- 64 | */ 65 | string color( 66 | const string str, 67 | const fg c=fg.init, 68 | const bg b=bg.init, 69 | const mode m=mode.init 70 | ) pure 71 | { 72 | import std.string : format; 73 | return format("\033[%d;%d;%dm%s\033[0m", m, c, b, str); 74 | } 75 | 76 | unittest 77 | { 78 | import std.string : representation; 79 | 80 | string ret; 81 | 82 | ret = "This is yellow".color(fg.yellow); 83 | assert(ret.representation == "\033[0;33;49mThis is yellow\033[0m".representation); 84 | 85 | ret = "This is light green".color(fg.light_green); 86 | assert(ret.representation == "\033[0;92;49mThis is light green\033[0m".representation); 87 | 88 | ret = "This is light blue with red background".color(fg.light_blue, bg.red); 89 | assert(ret.representation == "\033[0;94;41mThis is light blue with red background\033[0m".representation); 90 | 91 | ret = "This is red on blue blinking".color(fg.red, bg.blue, mode.blink); 92 | assert(ret.representation == "\033[5;31;44mThis is red on blue blinking\033[0m".representation); 93 | } 94 | 95 | string colorHelper(T)(const string str, const T t=T.init) pure 96 | if(is(T : fg) || is(T : bg) || is(T : mode)) 97 | { 98 | import std.string : format; 99 | return format("\033[%dm%s\033[0m", t, str); 100 | } 101 | 102 | alias background = colorHelper!bg; 103 | alias foreground = colorHelper!fg; 104 | alias style = colorHelper!mode; 105 | alias color = colorHelper; 106 | 107 | unittest 108 | { 109 | import std.string : representation; 110 | 111 | string ret; 112 | 113 | ret = "This is red on blue blinking" 114 | .foreground(fg.red) 115 | .background(bg.blue) 116 | .style(mode.blink); 117 | assert(ret.representation == "\033[5m\033[44m\033[31mThis is red on blue blinking\033[0m\033[0m\033[0m".representation); 118 | } -------------------------------------------------------------------------------- /colorize/source/redub/libs/colorize/cwrite.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Authors: ponce 3 | * Date: July 28, 2014 4 | * License: Licensed under the MIT license. See LICENSE for more information 5 | * Version: 1.0.2 6 | */ 7 | module redub.libs.colorize.cwrite; 8 | import std.stdio : File, stdout; 9 | import redub.libs.colorize.winterm; 10 | 11 | /// Coloured write. 12 | void cwrite(T...)(T args) if (!is(T[0] : File)) 13 | { 14 | stdout.cwrite(args); 15 | } 16 | 17 | /// Coloured writef. 18 | void cwritef(Char, T...)(in Char[] fmt, T args) if (!is(T[0] : File)) 19 | { 20 | stdout.cwritef(fmt, args); 21 | } 22 | 23 | /// Coloured writefln. 24 | void cwritefln(Char, T...)(in Char[] fmt, T args) 25 | { 26 | stdout.cwritef(fmt ~ "\n", args); 27 | } 28 | 29 | /// Coloured writeln. 30 | void cwriteln(T...)(T args) 31 | { 32 | // Most general instance 33 | stdout.cwrite(args, '\n'); 34 | } 35 | 36 | 37 | /// Coloured writef to a File. 38 | void cwrite(S...)(File f, S args) 39 | { 40 | import std.conv : to; 41 | 42 | string s = ""; 43 | foreach(arg; args) 44 | s ~= to!string(arg); 45 | 46 | version(Windows) 47 | { 48 | WinTermEmulation winterm; 49 | winterm.initialize(); 50 | foreach(dchar c ; s) 51 | { 52 | auto charAction = winterm.feed(c); 53 | final switch(charAction) with (WinTermEmulation.CharAction) 54 | { 55 | case drop: break; 56 | case write: f.write(c); break; 57 | case flush: f.flush(); break; 58 | } 59 | } 60 | } 61 | else 62 | { 63 | f.write(s); 64 | } 65 | } -------------------------------------------------------------------------------- /colorize/source/redub/libs/colorize/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Authors: ponce 3 | * Date: July 28, 2014 4 | * License: Licensed under the MIT license. See LICENSE for more information 5 | * Version: 1.0.2 6 | */ 7 | module redub.libs.colorize; 8 | 9 | public import redub.libs.colorize.colors; 10 | public import redub.libs.colorize.cwrite; -------------------------------------------------------------------------------- /colorize/source/redub/libs/colorize/winterm.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Authors: ponce 3 | * Date: July 28, 2014 4 | * License: Licensed under the MIT license. See LICENSE for more information 5 | * Version: 1.0.2 6 | */ 7 | module redub.libs.colorize.winterm; 8 | 9 | version(Windows) 10 | { 11 | import core.sys.windows.windows; 12 | 13 | // Patch for DMD 2.065 compatibility 14 | static if( __VERSION__ < 2066 ) private enum nogc = 1; 15 | 16 | // This is a state machine to enable terminal colors on Windows. 17 | // Parses and interpret ANSI/VT100 Terminal Control Escape Sequences. 18 | // Only supports colour sequences, will output char incorrectly on invalid input. 19 | struct WinTermEmulation 20 | { 21 | public: 22 | @nogc void initialize() nothrow 23 | { 24 | // saves console attributes 25 | _console = GetStdHandle(STD_OUTPUT_HANDLE); 26 | _savedInitialColor = (0 != GetConsoleScreenBufferInfo(_console, &consoleInfo)); 27 | _state = State.initial; 28 | } 29 | 30 | @nogc ~this() nothrow 31 | { 32 | // Restore initial text attributes on release 33 | if (_savedInitialColor) 34 | { 35 | SetConsoleTextAttribute(_console, consoleInfo.wAttributes); 36 | _savedInitialColor = false; 37 | } 38 | } 39 | 40 | enum CharAction 41 | { 42 | write, 43 | drop, 44 | flush 45 | } 46 | 47 | // Eat one character and update color state accordingly. 48 | // Returns what to do with the fed character. 49 | @nogc CharAction feed(dchar d) nothrow 50 | { 51 | final switch(_state) with (State) 52 | { 53 | case initial: 54 | if (d == '\x1B') 55 | { 56 | _state = escaped; 57 | return CharAction.flush; 58 | } 59 | break; 60 | 61 | case escaped: 62 | if (d == '[') 63 | { 64 | _state = readingAttribute; 65 | _parsedAttr = 0; 66 | return CharAction.drop; 67 | } 68 | break; 69 | 70 | case readingAttribute: 71 | if (d >= '0' && d <= '9') 72 | { 73 | _parsedAttr = _parsedAttr * 10 + (d - '0'); 74 | return CharAction.drop; 75 | } 76 | else if (d == ';') 77 | { 78 | executeAttribute(_parsedAttr); 79 | _parsedAttr = 0; 80 | return CharAction.drop; 81 | } 82 | else if (d == 'm') 83 | { 84 | executeAttribute(_parsedAttr); 85 | _state = State.initial; 86 | return CharAction.drop; 87 | } 88 | break; 89 | } 90 | 91 | return CharAction.write; 92 | } 93 | 94 | private: 95 | HANDLE _console; 96 | bool _savedInitialColor; 97 | CONSOLE_SCREEN_BUFFER_INFO consoleInfo; 98 | State _state; 99 | WORD _currentAttr; 100 | int _parsedAttr; 101 | 102 | enum State 103 | { 104 | initial, 105 | escaped, 106 | readingAttribute 107 | } 108 | 109 | @nogc void setForegroundColor(WORD fgFlags) nothrow 110 | { 111 | _currentAttr = _currentAttr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); 112 | _currentAttr = _currentAttr | fgFlags; 113 | SetConsoleTextAttribute(_console, _currentAttr); 114 | } 115 | 116 | @nogc void setBackgroundColor(WORD bgFlags) nothrow 117 | { 118 | _currentAttr = _currentAttr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); 119 | _currentAttr = _currentAttr | bgFlags; 120 | SetConsoleTextAttribute(_console, _currentAttr); 121 | } 122 | 123 | // resets to the same foreground color that was set on initialize() 124 | @nogc void resetForegroundColor() nothrow 125 | { 126 | if (!_savedInitialColor) 127 | return; 128 | 129 | _currentAttr = _currentAttr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); 130 | _currentAttr = _currentAttr | (consoleInfo.wAttributes & (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY)); 131 | SetConsoleTextAttribute(_console, _currentAttr); 132 | } 133 | 134 | // resets to the same background color that was set on initialize() 135 | @nogc void resetBackgroundColor() nothrow 136 | { 137 | if (!_savedInitialColor) 138 | return; 139 | 140 | _currentAttr = _currentAttr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); 141 | _currentAttr = _currentAttr | (consoleInfo.wAttributes & (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY)); 142 | SetConsoleTextAttribute(_console, _currentAttr); 143 | } 144 | 145 | @nogc void executeAttribute(int attr) nothrow 146 | { 147 | switch (attr) 148 | { 149 | case 0: 150 | // reset all attributes 151 | SetConsoleTextAttribute(_console, consoleInfo.wAttributes); 152 | break; 153 | 154 | default: 155 | if ( (30 <= attr && attr <= 37) || (90 <= attr && attr <= 97) ) 156 | { 157 | WORD color = 0; 158 | if (90 <= attr && attr <= 97) 159 | { 160 | color = FOREGROUND_INTENSITY; 161 | attr -= 60; 162 | } 163 | attr -= 30; 164 | color |= (attr & 1 ? FOREGROUND_RED : 0) | (attr & 2 ? FOREGROUND_GREEN : 0) | (attr & 4 ? FOREGROUND_BLUE : 0); 165 | setForegroundColor(color); 166 | } 167 | else if (attr == 39) // fg.init 168 | { 169 | resetForegroundColor(); 170 | } 171 | 172 | if ( (40 <= attr && attr <= 47) || (100 <= attr && attr <= 107) ) 173 | { 174 | WORD color = 0; 175 | if (100 <= attr && attr <= 107) 176 | { 177 | color = BACKGROUND_INTENSITY; 178 | attr -= 60; 179 | } 180 | attr -= 40; 181 | color |= (attr & 1 ? BACKGROUND_RED : 0) | (attr & 2 ? BACKGROUND_GREEN : 0) | (attr & 4 ? BACKGROUND_BLUE : 0); 182 | setBackgroundColor(color); 183 | } 184 | else if (attr == 49) // bg.init 185 | { 186 | resetBackgroundColor(); 187 | } 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /d_dependencies/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /d_dependencies 6 | d_dependencies.so 7 | d_dependencies.dylib 8 | d_dependencies.dll 9 | d_dependencies.a 10 | d_dependencies.lib 11 | d_dependencies-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /d_dependencies/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo" 4 | ], 5 | "configurations": [ 6 | { 7 | "name": "library", 8 | "targetType": "library" 9 | }, 10 | { 11 | "name": "test", 12 | "dflags": [ 13 | "-deps=simports/deps_file" 14 | ], 15 | "stringImportPaths": [ 16 | "simports" 17 | ], 18 | "targetType": "executable" 19 | } 20 | ], 21 | "copyright": "Copyright © 2024, Marcelo", 22 | "description": "A minimal D application.", 23 | "license": "proprietary", 24 | "name": "d_dependencies" 25 | } -------------------------------------------------------------------------------- /d_dependencies/source/d_depedencies.d: -------------------------------------------------------------------------------- 1 | module d_depedencies; 2 | 3 | struct ModuleDef 4 | { 5 | string modName; 6 | string modPath; 7 | ModuleDef[string] importedBy; 8 | ModuleDef[string] imports; 9 | 10 | package void addImport(ref ModuleDef imported) 11 | { 12 | imported.importedBy[modPath] = this; 13 | imports[imported.modPath] = imported; 14 | } 15 | } 16 | 17 | class ModuleParsing 18 | { 19 | ModuleDef[string] allModules; 20 | 21 | ModuleDef* getModuleInCache(string moduleName, string modulePath) 22 | { 23 | ModuleDef* ret = modulePath in allModules; 24 | if(!ret) 25 | { 26 | allModules[modulePath] = ModuleDef(moduleName, modulePath); 27 | ret = modulePath in allModules; 28 | } 29 | return ret; 30 | } 31 | 32 | ModuleDef[] findRoots() 33 | { 34 | ModuleDef[] ret; 35 | foreach(key, value; allModules) 36 | { 37 | if(value.importedBy.length == 0) 38 | ret~= value; 39 | } 40 | return ret; 41 | } 42 | ModuleDef[] findDependees(const string[] filesPath) 43 | { 44 | bool[string] visited; 45 | return findDependees(filesPath, visited); 46 | } 47 | 48 | ModuleDef[] findDependees(const string[] filesPath, ref bool[string] visited) 49 | { 50 | ModuleDef[] ret; 51 | static void findDependeesImpl(ModuleDef* input, ref ModuleDef[] ret, ref bool[string] visited) 52 | { 53 | foreach(modPath, ref modDef; input.importedBy) 54 | { 55 | if(!(modPath in visited)) 56 | { 57 | visited[modPath] = true; 58 | ret~= modDef; 59 | findDependeesImpl(&modDef, ret, visited); 60 | } 61 | } 62 | } 63 | foreach(filePath; filesPath) 64 | { 65 | ModuleDef* mod = filePath in allModules; 66 | if(mod == null) 67 | continue; 68 | visited[filePath] = true; 69 | ret~= *mod; 70 | findDependeesImpl(mod, ret, visited); 71 | } 72 | return ret; 73 | 74 | } 75 | } 76 | 77 | 78 | 79 | private void put(Q, T)(Q range, scope T[] args ...) if(is(T == U*, U)) 80 | { 81 | int i = 0; 82 | foreach(v; range) 83 | { 84 | if(i >= args.length) 85 | return; 86 | if(args[i] == null) 87 | { 88 | i++; 89 | continue; 90 | } 91 | *args[i] = v; 92 | i++; 93 | } 94 | } 95 | 96 | ModuleParsing parseDependencies(string deps, immutable scope string[] exclude...) 97 | { 98 | import std.string; 99 | import std.algorithm; 100 | ModuleParsing ret = new ModuleParsing(); 101 | ModuleDef* current; 102 | 103 | /** 104 | * When including a path from the deps flag, it also includes the selective imports. 105 | * (C:\\D\\dmd2\\windows\\bin64\\..\\..\\src\\druntime\\import\\core\\stdc\\string.d):memcpy,strncmp,strlen 106 | * 107 | * So, it must remove the parenthesis and selective import from the paths. 108 | * Params: 109 | * importPath = Import Path style in the way dmd -deps saves. 110 | * Returns: A cleaned path only string 111 | */ 112 | static string cleanImportPath(string importPath, bool isUsingWinSep) 113 | { 114 | if(importPath.length == 0) return null; 115 | if(importPath[0] != '(') return importPath; 116 | ptrdiff_t lastIndex = lastIndexOf(importPath, ')'); 117 | string ret = importPath[1..lastIndex]; 118 | if(isUsingWinSep) 119 | { 120 | import std.string; 121 | return replace(ret, "\\\\", "\\"); 122 | } 123 | return ret; 124 | } 125 | bool isUsingWindowsSep; 126 | bool hasCheckWindowsSep; 127 | outer: foreach(string line; splitter(deps, "\n")) 128 | { 129 | foreach(value; exclude) if(line.startsWith(value)) 130 | continue outer; 131 | if(!hasCheckWindowsSep) 132 | { 133 | isUsingWindowsSep = line.indexOf('\\') != -1; 134 | hasCheckWindowsSep = true; 135 | } 136 | 137 | ///(moduleName) (modulePath) (:) (private/public/string) (:) (importedName) ((importedPath) 138 | string modName, modPath, importType, importStatic, importedName, importedPath; 139 | int i = 0; 140 | 141 | foreach(part; splitter(line, " : ")) 142 | { 143 | auto infos = splitter(part, " "); 144 | switch(i) 145 | { 146 | case 0: 147 | infos.put(&modName, &modPath); 148 | break; 149 | case 1: 150 | infos.put(&importType, &importStatic); 151 | break; 152 | case 2: 153 | infos.put(&importedName, &importedPath); 154 | break; 155 | case 3: break; //object (/Library/D/dmd/src/druntime/import/object.d) : public : core.attribute (/Library/D/dmd/src/druntime/import/core/attribute.d):selector 156 | default: 157 | import std.stdio; 158 | writeln("Unexpected format received with line '"~line); 159 | writeln(infos); 160 | throw new Exception("Information received: input name: "~modName~" input path:" ~ modPath~" importType: "~importType~" importStatic: "~importStatic~" importedName: "~importedName~" importedPath:"~importedPath); 161 | } 162 | i++; 163 | } 164 | importedPath = cleanImportPath(importedPath, isUsingWindowsSep); 165 | 166 | 167 | if(current == null || modName != current.modName) 168 | { 169 | modPath = cleanImportPath(modPath, isUsingWindowsSep); 170 | current = ret.getModuleInCache(modName.dup, isUsingWindowsSep ? modPath : modPath.idup); 171 | } 172 | 173 | ModuleDef* importedRef = ret.getModuleInCache(importedName, importedPath); 174 | current.addImport(*importedRef); 175 | } 176 | return ret; 177 | } 178 | 179 | unittest 180 | { 181 | import std.stdio; 182 | immutable string testcase = import("dub.deps"); 183 | ModuleParsing p = parseDependencies(testcase); 184 | foreach(ModuleDef v; p.allModules) 185 | { 186 | // writeln(v.modName); 187 | } 188 | // foreach(dep; p.findDependees("D:\\\\HipremeEngine\\\\source\\\\hip\\\\global\\\\gamedef.d")) 189 | // writeln(dep.modName); 190 | } 191 | 192 | unittest 193 | { 194 | import std.stdio; 195 | immutable string testcase = `core.internal.hash (C:\\D\\dmd2\\windows\\bin64\\..\\..\\src\\druntime\\import\\core\\internal\\hash.d) : private : object (C:\\D\\dmd2\\windows\\bin64\\..\\..\\src\\druntime\\import\\object.d)`; 196 | ModuleParsing p = parseDependencies(testcase); 197 | foreach(ModuleDef v; p.allModules) 198 | { 199 | // writeln(v.modName); 200 | } 201 | // foreach(dep; p.findDependees("D:\\\\HipremeEngine\\\\source\\\\hip\\\\global\\\\gamedef.d")) 202 | // writeln(dep.modName); 203 | } 204 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redub", 3 | "description": "Dub Based Build System, with parallelization per packages and easier to contribute", 4 | "authors": ["Hipreme"], 5 | "targetPath": "build", 6 | "stringImportPaths": [ 7 | "source" 8 | ], 9 | "configurations": [ 10 | { 11 | "name": "cli", 12 | "targetType": "executable" 13 | }, 14 | { 15 | "name": "cli-dev", 16 | "versions": ["Developer"], 17 | "targetType": "executable" 18 | }, 19 | { 20 | "name": "library", 21 | "targetType": "staticLibrary", 22 | "versions": ["AsLibrary"], 23 | "excludedSourceFiles": ["source/app.d"] 24 | } 25 | ], 26 | "license": "MIT", 27 | "dependencies": { 28 | "semver": {"path": "semver", "version": "*"}, 29 | "colorize": {"path": "colorize", "version": "*"}, 30 | "adv_diff": {"path": "adv_diff", "version": "*"}, 31 | "hipjson": {"path": "hipjson", "version": "*"}, 32 | "d_dependencies": {"path": "d_dependencies", "version": "*"}, 33 | "dub_sdl_to_json": {"path": "dub_sdl_to_json", "version": "*"}, 34 | "package_suppliers": {"path": "package_suppliers", "version": "*"}, 35 | "xxhash3": "~>0.0.5" 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "adv_diff": {"path":"adv_diff"}, 5 | "colorize": {"path":"colorize"}, 6 | "d_dependencies": {"path":"d_dependencies"}, 7 | "dub_sdl_to_json": {"path":"dub_sdl_to_json"}, 8 | "hipjson": {"path":"hipjson"}, 9 | "package_suppliers": {"path":"package_suppliers"}, 10 | "sdlite": "1.3.0", 11 | "semver": {"path":"semver"}, 12 | "taggedalgebraic": "0.11.23", 13 | "xxhash3": "0.0.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dub_sdl_to_json/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /dub_sdl_to_json 6 | dub_sdl_to_json.so 7 | dub_sdl_to_json.dylib 8 | dub_sdl_to_json.dll 9 | dub_sdl_to_json.a 10 | dub_sdl_to_json.lib 11 | dub_sdl_to_json-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /dub_sdl_to_json/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo" 4 | ], 5 | "dependencies": { 6 | "sdlite": "~>1.3.0", 7 | "hipjson": {"path": "../hipjson"} 8 | }, 9 | "copyright": "Copyright © 2024, Marcelo", 10 | "description": "A minimal D application.", 11 | "license": "proprietary", 12 | "name": "dub_sdl_to_json" 13 | } -------------------------------------------------------------------------------- /dub_sdl_to_json/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "hipjson": {"path":"../hipjson"}, 5 | "sdlite": "1.3.0", 6 | "taggedalgebraic": "0.11.23" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hipjson/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /hipjson 6 | hipjson.so 7 | hipjson.dylib 8 | hipjson.dll 9 | hipjson.a 10 | hipjson.lib 11 | hipjson-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /hipjson/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Hipreme" 4 | ], 5 | "copyright": "Copyright © 2024, Hipreme", 6 | "description": "HipJSON parser. Faster than STD one.", 7 | "license": "proprietary", 8 | "name": "hipjson" 9 | } -------------------------------------------------------------------------------- /package_suppliers/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /package_manager 6 | package_manager.so 7 | package_manager.dylib 8 | package_manager.dll 9 | package_manager.a 10 | package_manager.lib 11 | package_manager-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /package_suppliers/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo" 4 | ], 5 | "dependencies": { 6 | "hipjson": {"path": "../hipjson"}, 7 | "semver": {"path": "../semver"} 8 | }, 9 | "copyright": "Copyright © 2024, Marcelo", 10 | "description": "A minimal D application.", 11 | "license": "proprietary", 12 | "name": "package_suppliers" 13 | } -------------------------------------------------------------------------------- /package_suppliers/source/redub/libs/package_suppliers/dub_registry.d: -------------------------------------------------------------------------------- 1 | module redub.libs.package_suppliers.dub_registry; 2 | import redub.libs.package_suppliers.utils; 3 | import redub.libs.semver; 4 | import hipjson; 5 | package enum PackagesPath = "packages"; 6 | 7 | 8 | 9 | /** 10 | Online registry based package supplier. 11 | 12 | This package supplier connects to an online registry (e.g. 13 | $(LINK https://code.dlang.org/)) to search for available packages. 14 | */ 15 | class RegistryPackageSupplier 16 | { 17 | 18 | import std.uri : encodeComponent; 19 | string registryUrl; 20 | 21 | this(string registryUrl = "https://code.dlang.org/") 22 | { 23 | this.registryUrl = registryUrl; 24 | } 25 | 26 | SemVer getBestVersion(string packageName, SemVer requirement) 27 | { 28 | JSONValue md = getMetadata(packageName); 29 | if (md.type == JSONType.null_) 30 | return requirement; 31 | SemVer ret; 32 | 33 | foreach (json; md["versions"].array) 34 | { 35 | SemVer cur = SemVer(json["version"].str); 36 | if(cur.satisfies(requirement) && cur >= ret) 37 | ret = cur; 38 | } 39 | return ret; 40 | } 41 | 42 | /** 43 | * Used for diagnostics when the user typed a wrong version or inexistent packageName. 44 | * 45 | * Params: 46 | * packageName = The package to look for versions 47 | * Returns: An array of versions found 48 | */ 49 | SemVer[] getExistingVersions(string packageName) 50 | { 51 | JSONValue md = getMetadata(packageName); 52 | if(md.type == JSONType.null_) 53 | return null; 54 | SemVer[] ret; 55 | 56 | foreach(json; md["versions"].array) 57 | ret~= SemVer(json["version"].str); 58 | return ret; 59 | } 60 | 61 | string getPackageDownloadURL(string packageName, string version_) 62 | { 63 | return registryUrl~"packages/"~packageName~"/"~version_~".zip"; 64 | } 65 | 66 | string getBestPackageDownloadUrl(string packageName, SemVer requirement, out SemVer out_actualVersion) 67 | { 68 | JSONValue meta = getMetadata(packageName); 69 | if(meta.type == JSONType.null_) 70 | return null; 71 | out_actualVersion = getBestVersion(packageName, requirement); 72 | if(out_actualVersion == SemVer.init) 73 | return null; 74 | return getPackageDownloadURL(packageName, out_actualVersion.toString); 75 | } 76 | 77 | 78 | 79 | JSONValue getMetadata(string packageName) 80 | { 81 | import std.net.curl; 82 | static JSONValue[string] metadataCache; 83 | 84 | static string getMetadataUrl(string registryUrl, string packageName) 85 | { 86 | return registryUrl ~ "api/packages/infos?packages="~ 87 | encodeComponent(`["`~packageName~`"]`)~ 88 | "&include_dependencies=true&minimize=true"; 89 | } 90 | 91 | if(packageName in metadataCache) 92 | return metadataCache[packageName]; 93 | string data = cast(string)downloadFile(getMetadataUrl(registryUrl, packageName)); 94 | JSONValue parsed = parseJSON(data); 95 | foreach(k, v; parsed.object) 96 | metadataCache[k] = v; 97 | return metadataCache[packageName]; 98 | } 99 | 100 | } 101 | unittest 102 | { 103 | assert(new RegistryPackageSupplier().getPackageDownloadURL("redub", "1.16.0") == "https://code.dlang.org/packages/redub/1.16.0.zip"); 104 | assert(new RegistryPackageSupplier().getBestPackageDownloadUrl("redub", SemVer("1.16.0")) == "https://code.dlang.org/packages/redub/1.16.0.zip"); 105 | } 106 | 107 | //Speed test 108 | unittest 109 | { 110 | // import std.stdio; 111 | // import std.parallelism; 112 | // import std.datetime.stopwatch; 113 | 114 | // StopWatch sw = StopWatch(AutoStart.yes); 115 | // auto reg = new RegistryPackageSupplier(); 116 | 117 | // string[] packages = ["bindbc-sdl", "bindbc-common", "bindbc-opengl", "redub"]; 118 | // foreach(pkg; parallel(packages)) 119 | // { 120 | // reg.downloadPackageTo("dub/packages/"~pkg, pkg, SemVer(">=0.0.0")); 121 | // } 122 | // writeln("Fetched packages ", packages, " in ", sw.peek.total!"msecs", "ms"); 123 | } -------------------------------------------------------------------------------- /package_suppliers/source/redub/libs/package_suppliers/utils.d: -------------------------------------------------------------------------------- 1 | module redub.libs.package_suppliers.utils; 2 | 3 | 4 | class NetworkException : Exception 5 | { 6 | this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 7 | { 8 | super(msg, file, line, nextInChain); 9 | } 10 | } 11 | string getFirstFileInDirectory(string inputDir) 12 | { 13 | import std.file; 14 | if(!std.file.exists(inputDir)) 15 | return null; 16 | auto entries = dirEntries(inputDir,SpanMode.shallow); 17 | return entries.front; 18 | } 19 | 20 | ubyte[] downloadFile(string file) 21 | { 22 | import std.net.curl; 23 | HTTP http = HTTP(file); 24 | ubyte[] temp; 25 | http.onReceive = (ubyte[] data) 26 | { 27 | temp~= data; 28 | return data.length; 29 | }; 30 | http.perform(); 31 | return temp; 32 | } 33 | 34 | bool extractZipToFolder(ubyte[] data, string outputDirectory) 35 | { 36 | import std.file; 37 | import std.path; 38 | import std.zip; 39 | ZipArchive zip = new ZipArchive(data); 40 | if(!std.file.exists(outputDirectory)) 41 | std.file.mkdirRecurse(outputDirectory); 42 | foreach(string fileName, ArchiveMember archiveMember; zip.directory) 43 | { 44 | string outputFile = buildNormalizedPath(outputDirectory, fileName); 45 | if(!std.file.exists(outputFile)) 46 | { 47 | if(archiveMember.expandedSize == 0) 48 | std.file.mkdirRecurse(outputFile); 49 | else 50 | { 51 | string currentDirName = outputFile.dirName; 52 | if(!std.file.exists(currentDirName)) 53 | std.file.mkdirRecurse(currentDirName); 54 | std.file.write(outputFile, zip.expand(archiveMember)); 55 | } 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | 62 | bool extractZipToFolder(string zipPath, string outputDirectory) 63 | { 64 | import std.file; 65 | return extractZipToFolder(cast(ubyte[])std.file.read(zipPath), outputDirectory); 66 | } 67 | -------------------------------------------------------------------------------- /plugins/getmodules/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /getmodules 6 | getmodules.so 7 | getmodules.dylib 8 | getmodules.dll 9 | getmodules.a 10 | getmodules.lib 11 | getmodules-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | *.exp 18 | *.ilk 19 | -------------------------------------------------------------------------------- /plugins/getmodules/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo" 4 | ], 5 | "copyright": "Copyright © 2024, Marcelo", 6 | "description": "A minimal D application.", 7 | "license": "proprietary", 8 | "name": "getmodules" 9 | } -------------------------------------------------------------------------------- /plugins/getmodules/source/getmodules.d: -------------------------------------------------------------------------------- 1 | module getmodules; 2 | import redub.plugin.api; 3 | 4 | 5 | class GetModulePlugin : RedubPlugin 6 | { 7 | 8 | void preGenerate() 9 | { 10 | 11 | } 12 | 13 | void postGenerate() 14 | { 15 | 16 | } 17 | /** 18 | * Simple utility file to get the source files used for a game project in Hipreme Engine. 19 | * This utility may be replaced in the future to prefer dub describe --data=source-files 20 | */ 21 | extern(C) ref RedubPluginStatus preBuild(RedubPluginData input, out RedubPluginData output, const ref string[] args, ref return RedubPluginStatus status) 22 | { 23 | import std.file; 24 | import std.path; 25 | import std.array:replace; 26 | 27 | if(args.length != 1) 28 | return status = RedubPluginStatus(RedubPluginExitCode.error, "Usage: rdmd gemodules outputFileName"); 29 | string outputPath = args[0]; 30 | 31 | if(exists(outputPath) && isDir(outputPath)) 32 | return status = RedubPluginStatus(RedubPluginExitCode.error, "Invalid output path '"~outputPath~"', the output path is a directory"); 33 | if(outputPath.length == 0) 34 | return status = RedubPluginStatus(RedubPluginExitCode.error, "Invalid output path '"~outputPath~"', the output path is empty."); 35 | 36 | string getModulesFile; 37 | 38 | foreach(string inputPath; input.sourcePaths) 39 | foreach(DirEntry e; dirEntries(inputPath, "*.d", SpanMode.depth)) 40 | { 41 | string file = e.name; 42 | if(getModulesFile != "") 43 | getModulesFile~="\n"; 44 | //Remove .d, change / or \ to . 45 | 46 | file = relativePath(file, inputPath)[0..$-2]; 47 | 48 | getModulesFile~= file.replace('/', '.').replace('\\', '.'); 49 | } 50 | 51 | string outDir = dirName(outputPath); 52 | if(!std.file.exists(outDir)) 53 | std.file.mkdirRecurse(outDir); 54 | 55 | std.file.write(outputPath, getModulesFile); 56 | return status = RedubPluginStatus(RedubPluginExitCode.success, "getModule plugin generated file "~outputPath); 57 | } 58 | 59 | void postBuild() 60 | { 61 | 62 | } 63 | } 64 | 65 | mixin PluginEntrypoint!(GetModulePlugin); -------------------------------------------------------------------------------- /redub.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": 8 | { 9 | "d.manyProjectsThreshold": 12, 10 | "d.disabledRootGlobs": ["tests/*"] 11 | } 12 | } -------------------------------------------------------------------------------- /replace_redub.bat: -------------------------------------------------------------------------------- 1 | SET NEW_REDUB=%1 2 | SET OLD_REDUB=%2 3 | 4 | :WAIT_FOR_PARENT 5 | tasklist | findstr /i "redub.exe" >nul 6 | if %ERRORLEVEL%==0 ( 7 | rem Redub is still running... 8 | timeout /t 1 9 | goto WAIT_FOR_PARENT 10 | ) 11 | copy /Y %NEW_REDUB% %OLD_REDUB% -------------------------------------------------------------------------------- /replace_redub.sh: -------------------------------------------------------------------------------- 1 | PARENT_PID=$1 2 | NEW_REDUB=$2 3 | OLD_REDUB=$3 4 | 5 | while ps -p $PARENT_PID > /dev/null; do 6 | sleep 1 7 | done 8 | cp -f $NEW_REDUB $OLD_REDUB 9 | chmod +x $OLD_REDUB #Old is now the now one -------------------------------------------------------------------------------- /semver/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /semver 6 | semver.so 7 | semver.dylib 8 | semver.dll 9 | semver.a 10 | semver.lib 11 | semver-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /semver/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 6 | "description": "A minimal SemVer implementation, almost completely @nogc and nothrow", 7 | "targetType": "library", 8 | "license": "proprietary", 9 | "name": "semver" 10 | } -------------------------------------------------------------------------------- /semver/source/redub/libs/version_parser.d: -------------------------------------------------------------------------------- 1 | module redub.libs.version_parser; 2 | import std.typecons; 3 | 4 | package alias nint = Nullable!int; 5 | 6 | nint[3] parseVersion(string verString, out ptrdiff_t currIndex) 7 | { 8 | import std.ascii; 9 | import std.algorithm.iteration; 10 | import std.conv:to; 11 | nint[3] ret; 12 | 13 | 14 | ubyte retIndex = 0; 15 | ptrdiff_t i = -1; 16 | 17 | foreach(value; splitter(verString, '.')) 18 | { 19 | i = 0; 20 | while(i < value.length && isDigit(value[i])) 21 | i++; 22 | if(i != 0) 23 | ret[retIndex] = value[0..i].to!int; 24 | else 25 | return ret; 26 | 27 | retIndex++; 28 | if(retIndex == 3) 29 | { 30 | currIndex+= i; 31 | break; 32 | } 33 | else 34 | { 35 | currIndex+= i; 36 | if(currIndex < verString.length) 37 | currIndex+= 1; //Advance the dot 38 | } 39 | } 40 | return ret; 41 | } 42 | 43 | size_t versionsCount(nint[3] ver) 44 | { 45 | if(ver[0].isNull) return 0; 46 | if(ver[1].isNull) return 1; 47 | if(ver[2].isNull) return 2; 48 | return 3; 49 | } -------------------------------------------------------------------------------- /source/redub/building/utils.d: -------------------------------------------------------------------------------- 1 | module redub.building.utils; 2 | import redub.buildapi; 3 | 4 | auto execCompilerBase(const BuildConfiguration cfg, string compilerBin, string[] compileFlags, out string compilationCommands, bool isDCompiler) 5 | { 6 | import std.system; 7 | import std.process; 8 | import std.file; 9 | import redub.command_generators.automatic; 10 | import redub.command_generators.commons; 11 | if(std.system.os.isWindows && isDCompiler) 12 | { 13 | string cmdFile = createCommandFile(cfg, compileFlags, compilationCommands); 14 | compilationCommands = compilerBin ~ " "~compilationCommands; 15 | scope(exit) 16 | std.file.remove(cmdFile); 17 | return executeShell(compilerBin~ " @"~cmdFile, null, Config.none, size_t.max, cfg.workingDir); 18 | } 19 | compilationCommands = escapeCompilationCommands(compilerBin, compileFlags); 20 | return executeShell(compilationCommands, null, Config.none, size_t.max, cfg.workingDir); 21 | } 22 | 23 | auto execCompiler(const BuildConfiguration cfg, string compilerBin, string[] compileFlags, out string compilationCommands, Compiler compiler, string inputDir) 24 | { 25 | import std.file; 26 | import redub.api; 27 | import std.path; 28 | 29 | import redub.compiler_identification; 30 | import redub.command_generators.commons; 31 | //Remove existing binary, since it won't be replaced by simply executing commands 32 | string outDir = getConfigurationOutputPath(cfg, os); 33 | if(exists(outDir)) 34 | remove(outDir); 35 | 36 | auto ret = execCompilerBase(cfg, compilerBin, compileFlags, compilationCommands, cfg.getCompiler(compiler).isDCompiler); 37 | 38 | if(ret.status == 0) 39 | { 40 | //For working around bug 3541, 24748, dmd generates .obj files besides files, redub will move them out 41 | //of there to the object directory 42 | if(cfg.outputsDeps && cfg.preservePath && cfg.getCompiler(compiler).compiler == AcceptedCompiler.dmd) 43 | moveGeneratedObjectFiles(cfg.sourcePaths, cfg.sourceFiles, cfg.excludeSourceFiles, getObjectDir(inputDir),getObjectExtension(os)); 44 | copyDir(inputDir, dirName(outDir)); 45 | } 46 | 47 | 48 | return ret; 49 | } 50 | 51 | auto linkBase(const ThreadBuildData data, CompilingSession session, string rootHash, out string compilationCommand) 52 | { 53 | import redub.command_generators.automatic; 54 | CompilerBinary c = data.cfg.getCompiler(session.compiler); 55 | return execCompilerBase( 56 | data.cfg, 57 | c.bin, 58 | getLinkFlags(data, session, rootHash), 59 | compilationCommand, 60 | c.isDCompiler, 61 | ); 62 | } 63 | 64 | /** 65 | * Generates a static library using archiver. FIXME: BuildRequirements should know its files. 66 | * Params: 67 | * data = The data containing project information 68 | * s = Compiling Session 69 | * command = Command for being able to print it later 70 | */ 71 | auto executeArchiver(const ThreadBuildData data, CompilingSession s, string mainPackHash, out string command) 72 | { 73 | import std.process; 74 | import std.array; 75 | import redub.command_generators.commons; 76 | import redub.compiler_identification; 77 | import std.path; 78 | import redub.building.cache; 79 | Archiver a = s.compiler.archiver; 80 | 81 | string[] cmd = [a.bin]; 82 | final switch(a.type) with(AcceptedArchiver) 83 | { 84 | case ar, llvmAr: cmd~= ["rcs"]; break; 85 | case libtool: cmd~= ["-static", "-o"]; break; 86 | case none: break; 87 | } 88 | 89 | cmd~= buildNormalizedPath(data.cfg.outputDirectory, getOutputName(data.cfg, s.os, s.isa)); 90 | 91 | string cacheDir = getCacheOutputDir(mainPackHash, data.cfg, s, data.extra.isRoot); 92 | 93 | putSourceFiles(cmd, null, [cacheDir], data.cfg.sourceFiles, data.cfg.excludeSourceFiles, ".o", ".obj"); 94 | command = cmd.join(" "); 95 | 96 | return executeShell(command); 97 | } -------------------------------------------------------------------------------- /source/redub/cli/dub.d: -------------------------------------------------------------------------------- 1 | module redub.cli.dub; 2 | 3 | /// The URL to the official package registry and it's default fallback registries. 4 | static immutable string[] defaultRegistryURLs = [ 5 | "https://code.dlang.org/", 6 | "https://codemirror.dlang.org/", 7 | "https://dub.bytecraft.nl/", 8 | "https://code-mirror.dlang.io/", 9 | ]; 10 | 11 | 12 | 13 | enum SkipRegistry 14 | { 15 | none, 16 | standard, 17 | configured, 18 | all 19 | } 20 | 21 | enum ParallelType 22 | { 23 | auto_ = "auto", 24 | full = "full", 25 | leaves = "leaves", 26 | no = "no" 27 | } 28 | 29 | enum Inference 30 | { 31 | auto_ = "auto", 32 | off = "off", 33 | on = "on" 34 | } 35 | 36 | enum Color 37 | { 38 | auto_ = "auto", 39 | always = "always", 40 | never = "never" 41 | } 42 | 43 | struct DubCommonArguments 44 | { 45 | @("Path to operate in instead of the current working dir") 46 | string root; 47 | 48 | @("Loads a custom recipe path instead of dub.json/dub.sdl") 49 | string recipe; 50 | 51 | @( 52 | "Search the given registry URL first when resolving dependencies. Can be specified multiple times. Available registry types:" ~ 53 | " DUB: URL to DUB registry (default)" ~ 54 | " Maven: URL to Maven repository + group id containing dub packages as artifacts. E.g. mvn+http://localhost:8040/maven/libs-release/dubpackages" 55 | ) 56 | string registry; 57 | 58 | @("Sets a mode for skipping the search on certain package registry types:" ~ 59 | " none: Search all configured or default registries (default)" ~ 60 | " standard: Don't search the main registry (e.g. "~defaultRegistryURLs[0]~")" ~ 61 | " configured: Skip all default and user configured registries"~ 62 | " all: Only search registries specified with --registry" 63 | ) 64 | @("skip-registry") 65 | SkipRegistry skipRegistry; 66 | 67 | @("Do not perform any action, just print what would be done") 68 | bool annotate; 69 | 70 | @("Read only packages contained in the current directory") 71 | bool bare; 72 | 73 | @("Print diagnostic output") 74 | @("v|verbose") 75 | bool verbose; 76 | @("Print debug output") 77 | bool vverbose; 78 | 79 | @("Only print warnings and errors") 80 | @("q|quiet") 81 | bool quiet; 82 | @("Only print errors") 83 | bool verror; 84 | @("Print no messages") 85 | bool vquiet; 86 | 87 | @("Configure colored output. Accepted values:"~ 88 | " auto: Colored output on console/terminal,"~ 89 | " unless NO_COLOR is set and non-empty (default)"~ 90 | " always: Force colors enabled"~ 91 | " never: Force colors disabled" 92 | ) 93 | Color color; 94 | 95 | @("Puts any fetched packages in the specified location [local|system|user].") 96 | string cache; 97 | 98 | string getRoot(string workingDir) const 99 | { 100 | import std.path; 101 | if(isAbsolute(root)) return root; 102 | return buildNormalizedPath(workingDir, root); 103 | } 104 | 105 | string getRecipe(string workingDir) const 106 | { 107 | import std.path; 108 | if(isAbsolute(recipe)) return recipe; 109 | return buildNormalizedPath(getRoot(workingDir), recipe); 110 | } 111 | } 112 | 113 | struct DubDescribeArguments 114 | { 115 | @( 116 | "The accepted values for --data=VALUE are: 117 | 118 | main-source-file, dflags, lflags, libs, linker-files, source-files, versions, 119 | debug-versions, import-paths, string-import-paths, import-files, options 120 | " 121 | ) 122 | string[] data; 123 | } 124 | 125 | struct DubArguments 126 | { 127 | DubCommonArguments cArgs; 128 | DubBuildArguments build; 129 | 130 | @("Specifies the type of build to perform. Note that setting the DFLAGS environment variable will override the build type with custom flags." ~ 131 | "Possible names:") 132 | @("build|b") 133 | string buildType; 134 | 135 | @("Builds the specified configuration. Configurations can be defined in dub.json") 136 | @("config|c") 137 | string config; 138 | 139 | @( 140 | "Specifies the compiler binary to use (can be a path)\n" ~ 141 | "Arbitrary pre- and suffixes to the identifiers below are recognized (e.g. ldc2 or dmd-2.063) and matched to the proper compiler type:\n" ~ 142 | "dmd, ldc (For the C Compilers, use -C or --cCompiler) gcc, g++ " 143 | ) 144 | @("dc|compiler") 145 | string compiler; 146 | 147 | @( 148 | "Specifies which C compiler binary to use "~ 149 | "The current supported C compilers are gcc and g++" 150 | ) 151 | @("cc|c-compiler") 152 | string cCompiler; 153 | 154 | 155 | @("Specifies where the target output is.") 156 | string targetPath; 157 | 158 | @("Specifies the target name") 159 | string targetName; 160 | 161 | @( 162 | "Specifies a version string which contains the compiler name and its version "~ 163 | "This can make the dependency resolution a lot faster since executing compiler --version won't be necessary "~ 164 | "Valid format: \"dmd v[2.106.0]\", \"ldc2 v[1.36.0] f[2.105.0]\" - v stands for compiler version, f for frontend" 165 | ) 166 | @("assume-compiler") 167 | string compilerAssumption; 168 | 169 | @("Force a different architecture (e.g. x86 or x86_64)") 170 | @("a|arch") 171 | string arch; 172 | 173 | @("Define the specified `debug` version identifier when building - can be used multiple times") 174 | @("d|debug") 175 | string[] debugVersions; 176 | 177 | @( 178 | "Define the specified `version` identifier when building - can be used multiple times."~ 179 | "Use sparingly, with great power comes great responsibility! For commonly used or combined versions "~ 180 | "and versions that dependees should be able to use, create configurations in your package." 181 | ) @("d-version") 182 | string[] versions; 183 | 184 | @("Do not resolve missing dependencies before building") 185 | bool nodeps; 186 | 187 | @("Specifies the way the compiler and linker are invoked. Valid values:" ~ 188 | " separate (default), allAtOnce, singleFile") 189 | @("build-mode") 190 | string buildMode; 191 | 192 | @("Treats the package name as a filename. The file must contain a package recipe comment.") 193 | string single; 194 | 195 | @("Shows redub version") 196 | @("version") 197 | bool version_; 198 | 199 | @("Deprecated option that does nothing.") 200 | @("force-remove") 201 | bool forceRemove; 202 | 203 | @("[Experimental] Filter version identifiers and debug version identifiers to improve build cache efficiency.") 204 | string[] filterVersions; 205 | } 206 | 207 | 208 | struct DubBuildArguments 209 | { 210 | @("Builds the project in the temp folder if possible.") 211 | @("temp-build") 212 | bool tempBuild; 213 | 214 | @("Use rdmd instead of directly invoking the compiler") 215 | bool rdmd; 216 | 217 | @("Uses breadth first search for source files, replicating how dub adds files to project. Might be a little slower") 218 | bool breadth; 219 | 220 | @("Lists the available build types and which one is the default.") 221 | @("print-builds") 222 | bool printBuilds; 223 | 224 | 225 | @("Forces a recompilation even if the target is up to date") 226 | @("f|force") 227 | bool force; 228 | 229 | @(`Automatic yes to prompts. Assume "yes" as answer to all interactive prompts.`) 230 | @("y/yes") 231 | bool yes; 232 | 233 | @("Don't enter interactive mode.") 234 | @("n|non-interactive") 235 | bool nonInteractive; 236 | 237 | @("Build incrementally. This usually works on a case basis, so for you case, disabling it might make it faster."~ 238 | "It is inferred to be incremental when dependencies count >= 3. Supports |auto|on|off|") 239 | @("incremental") 240 | Inference incremental; 241 | 242 | @("Build parallelization type. Supported options are |auto|full|leaves|no|. Default being auto. Full will attempt "~ 243 | " to build every dependency at the same time. Leaves will build in parallel the dependencies that has no dependency. No will "~ 244 | " build in single thread." 245 | ) 246 | @("parallel") 247 | ParallelType parallel; 248 | 249 | @( 250 | "Whether or not to use existing .obj/.o files on the root build. Supported options are |auto|on|off. "~ 251 | "Based on tests, it can bring faster compilation when your package is not using libraries. "~ 252 | "Auto will deactivate existing objects when your package has any lib dependencies, while keep it active when " ~ 253 | "no dependency is there" 254 | ) 255 | @("use-existing-obj") 256 | bool useExistingObj; 257 | 258 | @("Tries to build the whole project in a single compiler run") 259 | bool combined; 260 | 261 | @("Build all dependencies, even when main target is a static library.") 262 | bool deep; 263 | } 264 | 265 | 266 | 267 | auto betterGetopt(T)(ref string[] args, out T opts) if(is(T == struct)) 268 | { 269 | import std.getopt; 270 | alias _ = opts; 271 | return mixin("getopt(args, " ~ genGetoptCall!(T)("_") ~ ")"); 272 | } 273 | 274 | private string genGetoptCall(T)(string memberName) 275 | { 276 | import std.traits:isFunction; 277 | string ret; 278 | 279 | static foreach(mem; __traits(allMembers, T)) 280 | {{ 281 | alias member = __traits(getMember, T, mem); 282 | static if(!isFunction!(typeof(member))) 283 | { 284 | static if(is(typeof(member) == struct)) 285 | { 286 | ret~= genGetoptCall!(typeof(member))(memberName~"."~mem); 287 | } 288 | else 289 | { 290 | alias att = __traits(getAttributes, member); 291 | static if(att.length == 2) 292 | ret~= att[1].stringof ~ ", "~att[0].stringof; 293 | else 294 | ret~= mem.stringof~", "~att[0].stringof; 295 | ret~=", &"~memberName~"."~mem~", "; 296 | } 297 | } 298 | }} 299 | return ret; 300 | } -------------------------------------------------------------------------------- /source/redub/command_generators/automatic.d: -------------------------------------------------------------------------------- 1 | module redub.command_generators.automatic; 2 | public import std.system; 3 | public import redub.buildapi; 4 | public import redub.compiler_identification; 5 | 6 | static import redub.command_generators.gnu_based; 7 | static import redub.command_generators.dmd; 8 | static import redub.command_generators.ldc; 9 | 10 | string escapeCompilationCommands(string compilerBin, string[] flags) 11 | { 12 | import std.process; 13 | return escapeShellCommand(compilerBin) ~ " " ~ processFlags(flags); 14 | } 15 | 16 | /** 17 | * This must be used on Windows since they need a command file 18 | * Params: 19 | * cfg = the configuration that will be parsed 20 | * s = Session for determining how to build it, compiler, os and ISA matters 21 | * mainPackHash = This will be used as a directory in some of outputs 22 | * Returns: The compilation commands those arguments generates 23 | */ 24 | string[] getCompilationFlags(const BuildConfiguration cfg, CompilingSession s, string mainPackHash, bool isRoot) 25 | { 26 | import redub.command_generators.commons; 27 | switch(cfg.getCompiler(s.compiler).compiler) with(AcceptedCompiler) 28 | { 29 | case gxx: 30 | return redub.command_generators.gnu_based.parseBuildConfiguration(cfg, s, mainPackHash, isRoot, cppExt); 31 | case gcc: 32 | return redub.command_generators.gnu_based.parseBuildConfiguration(cfg, s, mainPackHash, isRoot, cExt); 33 | case dmd: 34 | return redub.command_generators.dmd.parseBuildConfiguration(cfg, s, mainPackHash, isRoot); 35 | case ldc2: 36 | return redub.command_generators.ldc.parseBuildConfiguration(cfg, s, mainPackHash, isRoot); 37 | default:throw new Exception("Unsupported compiler '"~cfg.getCompiler(s.compiler).bin~"'"); 38 | } 39 | } 40 | 41 | string[] getLinkFlags(const ThreadBuildData data, CompilingSession s, string mainPackHash) 42 | { 43 | import command_generators.linkers; 44 | version(Windows) 45 | return parseLinkConfigurationMSVC(data, s, mainPackHash); 46 | else 47 | return parseLinkConfiguration(data, s, mainPackHash); 48 | } 49 | 50 | 51 | string getLinkCommands(const ThreadBuildData data, CompilingSession s, string mainPackHash) 52 | { 53 | import std.process; 54 | string[] flags = getLinkFlags(data, s, mainPackHash); 55 | 56 | CompilerBinary c = data.cfg.getCompiler(s.compiler); 57 | if(c.compiler == AcceptedCompiler.invalid) 58 | throw new Exception("Unsupported compiler '" ~ c.bin~"'"); 59 | 60 | return escapeShellCommand(c.bin) ~ " "~ processFlags(flags); 61 | } 62 | 63 | 64 | /** 65 | * Executes escaleShellCommand for fixing issues such as -rpath=$ORIGIN expanding to -rpath="" which may cause some issues 66 | * this will guarantee that no command is expanded by the shell environment 67 | * Params: 68 | * flags = The compiler or linker flags 69 | */ 70 | private auto processFlags(string[] flags) 71 | { 72 | import std.algorithm.iteration; 73 | import std.array:join; 74 | import std.process; 75 | return (map!((string v) => escapeShellCommand(v))(flags)).join(" "); 76 | } -------------------------------------------------------------------------------- /source/redub/command_generators/d_compilers.d: -------------------------------------------------------------------------------- 1 | module redub.command_generators.d_compilers; 2 | import redub.buildapi; 3 | import redub.command_generators.commons; 4 | import redub.compiler_identification; 5 | import redub.command_generators.ldc; 6 | import redub.building.cache; 7 | 8 | string[] parseBuildConfiguration(AcceptedCompiler comp, const BuildConfiguration b, CompilingSession s, string mainPackhash, bool isRoot) 9 | { 10 | import redub.misc.path; 11 | string function(ValidDFlags) mapper = getFlagMapper(comp); 12 | 13 | 14 | string[] cmds = [mapper(ValidDFlags.enableColor)]; 15 | string preserve = mapper(ValidDFlags.preserveNames); 16 | if(preserve) cmds ~= preserve; 17 | with(b) 18 | { 19 | if(isDebug) cmds~= "-debug"; 20 | if(compilerVerbose) cmds~= mapper(ValidDFlags.verbose); 21 | if(compilerVerboseCodeGen) cmds~= mapper(ValidDFlags.verboseCodeGen); 22 | 23 | 24 | string cacheDir = getCacheOutputDir(mainPackhash, b, s, isRoot); 25 | ///Whenever a single output file is specified, DMD does not output obj files 26 | ///For LDC, it does output them anyway 27 | if(b.getCompiler(s.compiler).compiler == AcceptedCompiler.ldc2 && !b.outputsDeps) 28 | { 29 | import std.file; 30 | string ldcObjOutDir = escapePath(cacheDir~ "_obj"); 31 | mkdirRecurse(ldcObjOutDir); 32 | 33 | cmds~= mapper(ValidDFlags.objectDir) ~ ldcObjOutDir; 34 | } 35 | else if(b.outputsDeps) 36 | cmds~= mapper(ValidDFlags.objectDir)~getObjectDir(cacheDir).escapePath; 37 | 38 | cmds~= dFlags; 39 | if(comp == AcceptedCompiler.ldc2) 40 | { 41 | ///cmds~= "--cache-retrieval=hardlink"; // Doesn't work on Windows when using a multi drives projects 42 | cmds~= "--cache=.ldc2_cache"; 43 | cmds~= "--cache-prune"; 44 | } 45 | 46 | 47 | cmds = mapAppendPrefix(cmds, debugVersions, mapper(ValidDFlags.debugVersions), false); 48 | cmds = mapAppendPrefix(cmds, versions, mapper(ValidDFlags.versions), false); 49 | cmds = mapAppendPrefix(cmds, importDirectories, mapper(ValidDFlags.importPaths), true); 50 | 51 | cmds = mapAppendPrefix(cmds, stringImportPaths, mapper(ValidDFlags.stringImportPaths), true); 52 | 53 | if(changedBuildFiles.length) 54 | cmds~= changedBuildFiles; 55 | else 56 | putSourceFiles(cmds, workingDir, sourcePaths, sourceFiles, excludeSourceFiles, ".d"); 57 | 58 | string arch = mapArch(comp, b.arch); 59 | if(arch) 60 | cmds~= arch; 61 | 62 | if(targetType.isLinkedSeparately) 63 | cmds~= mapper(ValidDFlags.compileOnly); 64 | if(targetType.isStaticLibrary) 65 | cmds~= mapper(ValidDFlags.buildAsLibrary); 66 | else if(targetType == TargetType.dynamicLibrary) 67 | cmds~= mapper(ValidDFlags.buildAsShared); 68 | 69 | 70 | //Output path for libs must still be specified 71 | if(!b.outputsDeps || targetType.isStaticLibrary) 72 | cmds~= mapper(ValidDFlags.outputFile) ~ buildNormalizedPath(cacheDir, getConfigurationOutputName(b, s.os)).escapePath; 73 | 74 | if(b.outputsDeps) 75 | cmds~= mapper(ValidDFlags.deps) ~ (buildNormalizedPath(cacheDir)~".deps").escapePath; 76 | 77 | } 78 | 79 | return cmds; 80 | } 81 | 82 | string getTargetTypeFlag(TargetType t, AcceptedCompiler c) 83 | { 84 | auto mapper = getFlagMapper(c); 85 | switch(t) with(TargetType) 86 | { 87 | case executable, autodetect: return null; 88 | case library, staticLibrary: return mapper(ValidDFlags.buildAsLibrary); 89 | case dynamicLibrary: return mapper(ValidDFlags.buildAsShared); 90 | default: throw new Exception("Unsupported target type"); 91 | } 92 | } 93 | 94 | 95 | /** 96 | * 97 | * Params: 98 | * dflags = The dFlags which should contain link flags 99 | * Returns: Only the link flags. 100 | */ 101 | string[] filterLinkFlags(const string[] dflags) 102 | { 103 | import std.algorithm.iteration:filter; 104 | import std.array; 105 | auto filtered = dflags.filter!((df => isLinkerDFlag(df))); 106 | return cast(string[])(filtered.array); 107 | } 108 | 109 | ///Courtesy directly from dub 110 | bool isLinkerDFlag(string arg) 111 | { 112 | static bool startsWith(string input, string what) 113 | { 114 | if(input.length < what.length || what.length == 0) return false; 115 | return input[0..what.length] == what; 116 | } 117 | if (arg.length > 2 && arg[0..2] == "--") 118 | arg = arg[1..$]; // normalize to 1 leading hyphen 119 | 120 | switch (arg) { 121 | case "-g", "-gc", "-m32", "-m64", "-shared", "-lib", 122 | "-betterC", "-disable-linker-strip-dead", "-static": 123 | return true; 124 | default: 125 | return startsWith(arg, "-L") 126 | || startsWith(arg, "-Xcc=") 127 | || startsWith(arg, "-defaultlib=") 128 | || startsWith(arg, "-platformlib=") 129 | || startsWith(arg, "-flto") 130 | || startsWith(arg, "-fsanitize=") 131 | || startsWith(arg, "-gcc=") 132 | || startsWith(arg, "-link-") 133 | || startsWith(arg, "-linker=") 134 | || startsWith(arg, "-march=") 135 | || startsWith(arg, "-mscrtlib=") 136 | || startsWith(arg, "-mtriple="); 137 | } 138 | } 139 | 140 | string function(ValidDFlags) getFlagMapper(AcceptedCompiler comp) 141 | { 142 | switch(comp) 143 | { 144 | case AcceptedCompiler.dmd: return &dmdFlags; 145 | case AcceptedCompiler.ldc2: return &ldcFlags; 146 | case AcceptedCompiler.gcc, AcceptedCompiler.gxx: return &gccFlags; 147 | default: throw new Exception("Compiler sent is not a D compiler."); 148 | } 149 | } 150 | 151 | string dmdFlags(ValidDFlags flag) 152 | { 153 | final switch(flag) with (ValidDFlags) 154 | { 155 | case debugMode: return "-debug"; 156 | case debugInfo: return "-g"; 157 | case releaseMode: return "-release"; 158 | case optimize: return "-O"; 159 | case inline: return "-inline"; 160 | case noBoundsCheck: return "-noboundscheck"; 161 | case unittests: return "-unittest"; 162 | case syntaxOnly: return "-o-"; 163 | case profile: return "-profile"; 164 | case profileGC: return "-profile=gc"; 165 | case coverage: return "-cov"; 166 | case coverageCTFE: return "-cov=ctfe"; 167 | case mixinFile: return "-mixin=mixed_in.d"; 168 | case verbose: return "-v"; 169 | case verboseCodeGen: return "-vasm"; 170 | case timeTrace: return "-ftime-trace"; 171 | case timeTraceFile: return "-ftime-trace-file=trace.json"; 172 | case enableColor: return "-color=on"; 173 | case stringImportPaths: return "-J="; 174 | case versions: return "-version="; 175 | case debugVersions: return "-debug="; 176 | case importPaths: return "-I"; 177 | case objectDir: return "-od="; 178 | case outputFile: return "-of="; 179 | case buildAsLibrary: return "-lib"; 180 | case buildAsShared: return "-shared"; 181 | case compileOnly: return "-c"; 182 | case arch: throw new Exception("arch not supported by dmd."); 183 | case preserveNames: return "-op"; 184 | case deps: return "-deps="; 185 | } 186 | } 187 | 188 | string ldcFlags(ValidDFlags flag) 189 | { 190 | final switch(flag) with (ValidDFlags) 191 | { 192 | case debugMode: return "-d-debug"; 193 | case debugInfo: return "-g"; 194 | case releaseMode: return "-release"; 195 | case optimize: return "-O3"; 196 | case inline: return "-enable-inlining"; 197 | case noBoundsCheck: return "-boundscheck=off"; 198 | case unittests: return "-unittest"; 199 | case syntaxOnly: return "-o-"; 200 | case profile: return "-fdmd-trace-functions"; 201 | case profileGC: return ""; 202 | case coverage: return "-cov"; 203 | case coverageCTFE: return "-cov=ctfe"; 204 | case mixinFile: return "-mixin=mixed_in.d"; 205 | case verbose: return "-v"; 206 | case verboseCodeGen: return "--v-cg"; 207 | case timeTrace: return "--ftime-trace"; 208 | case timeTraceFile: return "--ftime-trace-file=trace.json"; 209 | case enableColor: return "--enable-color=true"; 210 | case stringImportPaths: return "-J="; 211 | case versions: return "--d-version="; 212 | case debugVersions: return "--d-debug="; 213 | case importPaths: return "-I"; 214 | case objectDir: return "--od="; 215 | case outputFile: return "--of="; 216 | case buildAsLibrary: return "--lib"; 217 | case buildAsShared: return "--shared"; 218 | case compileOnly: return "-c"; 219 | case arch: return "--mtriple="; 220 | case preserveNames: return "--oq"; 221 | case deps: return "--deps="; 222 | } 223 | } 224 | 225 | string gccFlags(ValidDFlags flag) 226 | { 227 | final switch(flag) with (ValidDFlags) 228 | { 229 | case debugMode: return null; 230 | case debugInfo: return "-g"; 231 | case releaseMode: return "-O3"; 232 | case optimize: return "-O3"; 233 | case inline: return "-finline"; 234 | case noBoundsCheck: return null; 235 | case unittests: return null; 236 | case syntaxOnly: return "-S"; 237 | case profile: return null; 238 | case profileGC: return null; 239 | case coverage: return null; 240 | case coverageCTFE: return null; 241 | case mixinFile: return null; 242 | case verbose: return "-v"; 243 | case verboseCodeGen: return null; 244 | case timeTrace: return null; 245 | case timeTraceFile: return null; 246 | case enableColor: return null; 247 | case stringImportPaths: return "-I"; 248 | case versions: return "-D"; 249 | case debugVersions: return "-D"; 250 | case importPaths: return "-I"; 251 | case objectDir: return null; 252 | case outputFile: return "-o"; 253 | case buildAsLibrary: return null; 254 | case buildAsShared: return "-shared"; 255 | case compileOnly: return "-c"; 256 | case arch: return null; 257 | case preserveNames: return null; 258 | case deps: return null; 259 | } 260 | } 261 | 262 | // Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor. 263 | version (Windows) 264 | private bool isWow64() { 265 | // See also: https://docs.microsoft.com/de-de/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo 266 | import core.sys.windows.winbase : GetNativeSystemInfo, SYSTEM_INFO; 267 | import core.sys.windows.winnt : PROCESSOR_ARCHITECTURE_AMD64; 268 | 269 | static bool result; 270 | static bool hasLoadedResult = false; 271 | 272 | // A process's architecture won't change over while the process is in memory 273 | // Return the cached result 274 | if(!hasLoadedResult) 275 | { 276 | SYSTEM_INFO systemInfo; 277 | GetNativeSystemInfo(&systemInfo); 278 | result = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; 279 | hasLoadedResult = true; 280 | } 281 | 282 | return result; 283 | } 284 | 285 | string mapArch(AcceptedCompiler compiler, string arch) 286 | { 287 | if(compiler == AcceptedCompiler.ldc2) 288 | { 289 | switch (arch) 290 | { 291 | case "": return null; 292 | case "x86": return "-march=x86"; 293 | case "x86_mscoff": return "-march=x86"; 294 | case "x86_64": return "-march=x86-64"; 295 | case "aarch64": return "-march=aarch64"; 296 | case "powerpc64": return "-march=powerpc64"; 297 | default: return "-mtriple="~arch; 298 | } 299 | } 300 | else if(compiler == AcceptedCompiler.dmd) 301 | { 302 | switch(arch) 303 | { 304 | case "x86", "x86_omf", "x86_mscoff": return "-m32"; 305 | case "x64", "x86_64": return "-m64"; 306 | default: 307 | { 308 | version(Windows) 309 | { 310 | return isWow64() ? "-m64": "-m32"; 311 | } 312 | else return null; 313 | } 314 | } 315 | } 316 | else throw new Exception("Unsupported compiler for mapping arch."); 317 | } 318 | 319 | enum ValidDFlags 320 | { 321 | @"buildOption" debugMode, 322 | @"buildOption" debugInfo, 323 | @"buildOption" releaseMode, 324 | @"buildOption" optimize, 325 | @"buildOption" inline, 326 | @"buildOption" noBoundsCheck, 327 | @"buildOption" unittests, 328 | @"buildOption" syntaxOnly, 329 | @"buildOption" profile, 330 | @"buildOption" profileGC, 331 | @"buildOption" coverage, 332 | @"buildOption" coverageCTFE, 333 | 334 | verbose, 335 | verboseCodeGen, 336 | timeTrace, 337 | timeTraceFile, 338 | mixinFile, 339 | enableColor, 340 | stringImportPaths, 341 | versions, 342 | debugVersions, 343 | importPaths, 344 | objectDir, 345 | outputFile, 346 | buildAsLibrary, 347 | buildAsShared, 348 | compileOnly, 349 | arch, 350 | preserveNames, 351 | deps 352 | } -------------------------------------------------------------------------------- /source/redub/command_generators/dmd.d: -------------------------------------------------------------------------------- 1 | module redub.command_generators.dmd; 2 | import redub.buildapi; 3 | import redub.command_generators.commons; 4 | import redub.command_generators.d_compilers; 5 | 6 | string[] parseBuildConfiguration(const BuildConfiguration b, CompilingSession s, string requirementCache, bool isRoot) 7 | { 8 | return redub.command_generators.d_compilers.parseBuildConfiguration(AcceptedCompiler.dmd, b, s, requirementCache, isRoot); 9 | } -------------------------------------------------------------------------------- /source/redub/command_generators/gnu_based.d: -------------------------------------------------------------------------------- 1 | module redub.command_generators.gnu_based; 2 | 3 | public import redub.buildapi; 4 | public import std.system; 5 | import redub.command_generators.commons; 6 | import redub.logging; 7 | import redub.building.cache; 8 | 9 | /// Parse G++ configuration 10 | string[] parseBuildConfiguration(const BuildConfiguration b, CompilingSession s, string mainPackhash, bool isRoot, const string[] extensions...) 11 | { 12 | import std.algorithm.iteration:map; 13 | import std.file; 14 | import redub.misc.path; 15 | 16 | string[] cmds; 17 | 18 | with(b) 19 | { 20 | if(isDebug) cmds~= "-g"; 21 | cmds~= "-r"; 22 | 23 | cmds = mapAppendPrefix(cmds, versions, "-D", false); 24 | cmds~= dFlags; 25 | cmds = mapAppendPrefix(cmds, importDirectories, "-I", true); 26 | putSourceFiles(cmds, workingDir, sourcePaths, sourceFiles, excludeSourceFiles, extensions); 27 | 28 | 29 | string outFlag = getTargetTypeFlag(targetType); 30 | string cacheDir = getCacheOutputDir(mainPackhash, b, s, isRoot); 31 | 32 | mkdirRecurse(cacheDir); 33 | if(outFlag) 34 | cmds~= outFlag; 35 | cmds~= "-o"; 36 | if(outFlag) 37 | cmds ~= buildNormalizedPath(cacheDir, getConfigurationOutputName(b, s.os)).escapePath; 38 | else 39 | cmds ~= buildNormalizedPath(cacheDir, getObjectOutputName(b, os)).escapePath; 40 | } 41 | 42 | return cmds; 43 | } 44 | 45 | string getTargetTypeFlag(TargetType o) 46 | { 47 | final switch(o) with(TargetType) 48 | { 49 | case invalid, none: throw new Exception("Invalid targetType"); 50 | case autodetect, executable, sourceLibrary, staticLibrary, library: return null; 51 | case dynamicLibrary: return "-shared"; 52 | } 53 | } -------------------------------------------------------------------------------- /source/redub/command_generators/ldc.d: -------------------------------------------------------------------------------- 1 | module redub.command_generators.ldc; 2 | public import redub.buildapi; 3 | public import std.system; 4 | import redub.command_generators.commons; 5 | import redub.command_generators.d_compilers; 6 | 7 | 8 | 9 | string[] parseBuildConfiguration(const BuildConfiguration b, CompilingSession s, string requirementCache, bool isRoot) 10 | { 11 | return redub.command_generators.d_compilers.parseBuildConfiguration(AcceptedCompiler.ldc2, b, s, requirementCache, isRoot); 12 | } 13 | -------------------------------------------------------------------------------- /source/redub/command_generators/linkers.d: -------------------------------------------------------------------------------- 1 | module command_generators.linkers; 2 | public import redub.compiler_identification; 3 | public import redub.buildapi; 4 | public import std.system; 5 | import redub.command_generators.commons; 6 | 7 | 8 | string[] parseLinkConfiguration(const ThreadBuildData data, CompilingSession s, string requirementCache) 9 | { 10 | import redub.misc.path; 11 | import redub.building.cache; 12 | string[] cmds; 13 | AcceptedLinker linker = s.compiler.linker; 14 | bool emitStartGroup = s.isa != ISA.webAssembly && linker != AcceptedLinker.ld64; 15 | 16 | 17 | const BuildConfiguration b = data.cfg; 18 | CompilerBinary c = b.getCompiler(s.compiler); 19 | with(b) 20 | { 21 | { 22 | import redub.command_generators.d_compilers; 23 | 24 | if (targetType.isLinkedSeparately) 25 | { 26 | string cacheDir = getCacheOutputDir(requirementCache, b, s, data.extra.isRoot); 27 | string objExtension = getObjectExtension(s.os); 28 | if(c.isDCompiler) 29 | cmds~= "-of"~buildNormalizedPath(cacheDir, getOutputName(b, s.os)).escapePath; 30 | else 31 | { 32 | cmds~= "-o"; 33 | cmds~= buildNormalizedPath(cacheDir, getOutputName(b, s.os)).escapePath; 34 | } 35 | if(b.outputsDeps) 36 | putSourceFiles(cmds, null, [getObjectDir(cacheDir)], null, null, objExtension); 37 | else 38 | cmds~= buildNormalizedPath(outputDirectory, targetName~objExtension).escapePath; 39 | } 40 | if(c.isDCompiler) 41 | { 42 | string arch = mapArch(c.compiler, b.arch); 43 | if(arch) 44 | cmds~= arch; 45 | } 46 | cmds~= filterLinkFlags(b.dFlags); 47 | } 48 | if(targetType == TargetType.dynamicLibrary) 49 | cmds~= getTargetTypeFlag(targetType, c); 50 | 51 | if (targetType.isLinkedSeparately) 52 | { 53 | //Only linux supports start/end group and no-as-needed. OSX does not 54 | if(emitStartGroup) 55 | { 56 | cmds~= "-L--no-as-needed"; 57 | cmds~= "-L--start-group"; 58 | } 59 | ///Use library full path for the base file 60 | cmds = mapAppendReverse(cmds, data.extra.librariesFullPath, (string l) => "-L"~getOutputName(TargetType.staticLibrary, l, s.os)); 61 | if(emitStartGroup) 62 | cmds~= "-L--end-group"; 63 | cmds = mapAppendPrefix(cmds, linkFlags, "-L", false); 64 | cmds = mapAppendPrefix(cmds, libraryPaths, "-L-L", true); 65 | cmds~= getLinkFiles(b.sourceFiles); 66 | cmds = mapAppend(cmds, libraries, (string l) => "-L-l"~stripLibraryExtension(l)); 67 | 68 | } 69 | } 70 | 71 | return cmds; 72 | } 73 | 74 | string[] parseLinkConfigurationMSVC(const ThreadBuildData data, CompilingSession s, string requirementCache) 75 | { 76 | import std.algorithm.iteration; 77 | import redub.misc.path; 78 | import std.string; 79 | import redub.building.cache; 80 | 81 | 82 | if(!s.os.isWindows) return parseLinkConfiguration(data, s, requirementCache); 83 | string[] cmds; 84 | const BuildConfiguration b = data.cfg; 85 | CompilerBinary c = b.getCompiler(s.compiler); 86 | with(b) 87 | { 88 | string cacheDir = getCacheOutputDir(requirementCache, b, s, data.extra.isRoot); 89 | cmds~= "-of"~buildNormalizedPath(cacheDir, getOutputName(b, s.os)).escapePath; 90 | string objExtension = getObjectExtension(s.os); 91 | if(b.outputsDeps) 92 | putSourceFiles(cmds, null, [getObjectDir(cacheDir)], null, null, objExtension); 93 | else 94 | cmds~= buildNormalizedPath(outputDirectory, targetName~objExtension).escapePath; 95 | 96 | if(c.isDCompiler) 97 | { 98 | import redub.command_generators.d_compilers; 99 | string arch = mapArch(c.compiler, b.arch); 100 | if(arch) 101 | cmds~= arch; 102 | cmds~= filterLinkFlags(b.dFlags); 103 | } 104 | if(!s.compiler.usesIncremental) 105 | { 106 | cmds~= "-L/INCREMENTAL:NO"; 107 | } 108 | 109 | if(targetType == TargetType.dynamicLibrary) 110 | cmds~= getTargetTypeFlag(targetType, c); 111 | 112 | cmds = mapAppendReverse(cmds, data.extra.librariesFullPath, (string l) => (l~getLibraryExtension(s.os)).escapePath); 113 | 114 | cmds = mapAppendPrefix(cmds, linkFlags, "-L", false); 115 | 116 | cmds = mapAppendPrefix(cmds, libraryPaths, "-L/LIBPATH:", true); 117 | cmds~= getLinkFiles(b.sourceFiles); 118 | cmds = mapAppend(cmds, libraries, (string l) => "-L"~stripLibraryExtension(l)~".lib"); 119 | 120 | } 121 | return cmds; 122 | } 123 | 124 | 125 | string getTargetTypeFlag(TargetType o, CompilerBinary compiler) 126 | { 127 | static import redub.command_generators.d_compilers; 128 | static import redub.command_generators.gnu_based; 129 | 130 | switch(compiler.compiler) with(AcceptedCompiler) 131 | { 132 | case dmd, ldc2: return redub.command_generators.d_compilers.getTargetTypeFlag(o, compiler.compiler); 133 | case gcc, gxx: return redub.command_generators.gnu_based.getTargetTypeFlag(o); 134 | default: throw new Exception("Unsupported compiler "~compiler.bin); 135 | } 136 | } -------------------------------------------------------------------------------- /source/redub/logging.d: -------------------------------------------------------------------------------- 1 | module redub.logging; 2 | import redub.libs.colorize; 3 | 4 | enum LogLevel 5 | { 6 | none, 7 | error, 8 | warn, 9 | info, 10 | verbose, 11 | vverbose 12 | } 13 | private LogLevel level; 14 | 15 | LogLevel getLogLevel(){ return level; } 16 | 17 | void inLogLevel(T)(LogLevel lvl, scope lazy T action) 18 | { 19 | if(hasLogLevel(lvl)) 20 | action; 21 | } 22 | 23 | bool hasLogLevel(LogLevel lvl) 24 | { 25 | return level >= lvl; 26 | } 27 | void setLogLevel(LogLevel lvl){ level = lvl; } 28 | void info(T...)(T args){if(level >= LogLevel.info) cwriteln(args);} 29 | ///Short for info success 30 | void infos(T...)(string greenMsg, T args){if(level >= LogLevel.info) cwriteln(greenMsg.color(fg.green) ,args);} 31 | void vlog(T...)(T args){if(level >= LogLevel.verbose) cwriteln(args);} 32 | void vvlog(T...)(T args){if(level >= LogLevel.vverbose) cwriteln(args);} 33 | void flush() 34 | { 35 | if(level != LogLevel.none) 36 | { 37 | import std.stdio; 38 | stdout.flush; 39 | } 40 | } 41 | void warn(T...)(T args){if(level >= LogLevel.warn)cwriteln("Warning: ".color(fg.yellow), args);} 42 | void warnTitle(T...)(string yellowMsg, T args){if(level >= LogLevel.warn)cwriteln(yellowMsg.color(fg.yellow), args);} 43 | void error(T...)(T args){if(level >= LogLevel.error)cwriteln("ERROR! ".color(fg.red), args);} 44 | void errorTitle(T...)(string redMsg, T args){if(level >= LogLevel.error)cwriteln(redMsg.color(fg.red), args);} -------------------------------------------------------------------------------- /source/redub/meta.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a way for accessing meta information on redub execution. 3 | * Things such as compiler information and redub version are saved here. 4 | * 5 | */ 6 | module redub.meta; 7 | public import hipjson; 8 | 9 | private string getRedubMetaFileName() 10 | { 11 | import redub.misc.path; 12 | import redub.api; 13 | static string redubCompilersFile; 14 | if(redubCompilersFile == null) 15 | redubCompilersFile = buildNormalizedPath(getDubWorkspacePath, "redub_meta.json"); 16 | return redubCompilersFile; 17 | } 18 | 19 | 20 | void saveRedubMeta(JSONValue content) 21 | { 22 | import redub.buildapi; 23 | import std.file; 24 | import std.path; 25 | string dir = dirName(getRedubMetaFileName); 26 | if(!std.file.exists(dir)) 27 | mkdirRecurse(dir); 28 | 29 | if(!("version" in content)) 30 | content["version"] = JSONValue(RedubVersionOnly); 31 | 32 | std.file.write(getRedubMetaFileName, content.toString); 33 | } 34 | 35 | JSONValue getRedubMeta() 36 | { 37 | import std.file; 38 | import redub.buildapi; 39 | static JSONValue meta; 40 | string metaFile = getRedubMetaFileName; 41 | if(meta == JSONValue.init) 42 | { 43 | if(exists(metaFile)) 44 | { 45 | meta = parseJSON(cast(string)std.file.read(getRedubMetaFileName)); 46 | if(meta.hasErrorOccurred) 47 | return JSONValue.emptyObject; 48 | JSONValue* ver = "version" in meta; 49 | if(ver == null || ver.str != RedubVersionOnly) 50 | return JSONValue.emptyObject; 51 | } 52 | else 53 | return JSONValue.emptyObject; 54 | } 55 | return meta; 56 | } 57 | 58 | string getExistingRedubVersion() 59 | { 60 | JSONValue redubMeta = getRedubMeta(); 61 | if(redubMeta.hasErrorOccurred) 62 | return null; 63 | JSONValue* ver = "version" in redubMeta; 64 | if(!ver) 65 | return null; 66 | return ver.str; 67 | } -------------------------------------------------------------------------------- /source/redub/misc/console_control_handler.d: -------------------------------------------------------------------------------- 1 | module redub.misc.console_control_handler; 2 | 3 | 4 | private void printPendingProjects() nothrow 5 | { 6 | try 7 | { 8 | import std.stdio; 9 | // writeln("Executed CTRL+C"); 10 | } 11 | catch(Exception e){} 12 | } 13 | 14 | void startHandlingConsoleControl() 15 | { 16 | static bool hasStarted = false; 17 | if(!hasStarted) 18 | { 19 | hasStarted = true; 20 | handleConsoleControl(); 21 | } 22 | } 23 | 24 | version(Windows) 25 | { 26 | import core.sys.windows.windef; 27 | private extern(Windows) BOOL handleConsoleControlWindows(DWORD ctrlType) nothrow 28 | { 29 | import core.sys.windows.wincon; 30 | switch ( ctrlType ) 31 | { 32 | case CTRL_C_EVENT: 33 | printPendingProjects(); 34 | return TRUE; 35 | default: 36 | return FALSE; 37 | } 38 | } 39 | private void handleConsoleControl() 40 | { 41 | import core.sys.windows.wincon; 42 | SetConsoleCtrlHandler(&handleConsoleControlWindows, 1); 43 | } 44 | } 45 | else version(Posix) 46 | { 47 | private extern(C) void handleConsoleControlPosix(int sig) 48 | { 49 | printPendingProjects(); 50 | } 51 | 52 | private void handleConsoleControl() 53 | { 54 | import core.sys.posix.signal; 55 | sigaction_t action; 56 | action.sa_handler = &handleConsoleControlPosix; 57 | sigaction(SIGINT, &action, null); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/redub/misc/file_size_format.d: -------------------------------------------------------------------------------- 1 | module redub.misc.file_size_format; 2 | 3 | string getFileSizeFormatted(string targetFile) 4 | { 5 | import std.conv; 6 | import std.file; 7 | 8 | static struct ByteUnit 9 | { 10 | double data; 11 | string unit; 12 | } 13 | 14 | static double floorDecimal(double value, ubyte decimals) 15 | { 16 | import std.math; 17 | double factor = pow(10, decimals); 18 | 19 | return floor(value * factor) / factor; 20 | } 21 | 22 | ByteUnit formatFromBytes(size_t byteCount) @nogc 23 | { 24 | double actualResult = byteCount; 25 | 26 | if(actualResult <= 1000) 27 | return ByteUnit(floorDecimal(actualResult, 2), " B"); 28 | actualResult/= 1000; 29 | if(actualResult <= 1000) 30 | return ByteUnit(floorDecimal(actualResult, 2), " KB"); 31 | actualResult/= 1000; 32 | return ByteUnit(floorDecimal(actualResult, 2), " MB"); 33 | actualResult/= 1000; 34 | return ByteUnit(floorDecimal(actualResult, 2), " GB"); 35 | } 36 | 37 | try 38 | { 39 | ByteUnit b = formatFromBytes(std.file.getSize(targetFile)); 40 | return to!string(b.data)~b.unit; 41 | } 42 | catch(Exception e) 43 | { 44 | return "File '"~targetFile~"' not found."; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /source/redub/misc/find_executable.d: -------------------------------------------------------------------------------- 1 | module redub.misc.find_executable; 2 | 3 | string findExecutable(string executableName) 4 | { 5 | import std.process; 6 | import std.path; 7 | import std.file; 8 | import std.algorithm.iteration:splitter; 9 | string pathEnv = environment.get("PATH", ""); 10 | 11 | version(Windows) 12 | static string[] EXTENSIONS = [".exe", ".bat", ".cmd", ".com", ""]; 13 | else 14 | static string[] EXTENSIONS = [""]; 15 | 16 | string[] extensionsTest = EXTENSIONS; 17 | if(extension(executableName) != null) 18 | extensionsTest = [""]; 19 | 20 | static bool isExecutable(string tPath) 21 | { 22 | version(Posix) 23 | { 24 | import std.string:toStringz; 25 | import core.sys.posix.sys.stat; 26 | stat_t stats; 27 | if(stat(toStringz(tPath), &stats) != 0) 28 | return false; 29 | 30 | static immutable flags = S_IXUSR | S_IXGRP | S_IXOTH; 31 | return (stats.st_mode & flags) == flags; 32 | } 33 | else return std.file.exists(tPath); 34 | } 35 | 36 | if(isAbsolute(executableName) && isExecutable(executableName)) 37 | return executableName; 38 | 39 | foreach(path; splitter(pathEnv, pathSeparator)) 40 | { 41 | 42 | foreach(ext; EXTENSIONS) 43 | { 44 | string fullPath = buildPath(path, executableName ~ ext); 45 | if(std.file.exists(fullPath)) 46 | return fullPath; 47 | } 48 | 49 | } 50 | return ""; 51 | } -------------------------------------------------------------------------------- /source/redub/misc/github_tag_check.d: -------------------------------------------------------------------------------- 1 | module redub.misc.github_tag_check; 2 | 3 | private enum RedubUserRepository = "MrcSnm/redub"; 4 | private enum GithubTagAPI = "https://api.github.com/repos/"~RedubUserRepository~"/tags"; 5 | private enum CreateIssueURL = "https://github.com/"~RedubUserRepository~"/issues/new/choose"; 6 | 7 | 8 | void showNewerVersionMessage() 9 | { 10 | import redub.buildapi; 11 | import redub.logging; 12 | string ver = getLatestVersion(); 13 | if(ver) 14 | { 15 | if(ver != RedubVersionOnly) 16 | { 17 | warnTitle( 18 | "Redub "~ ver~ " available. \n\t", 19 | "Maybe try updating it with 'redub update 'or running dub fetch redub@"~ver[1..$]~" if you think this compilation error is a redub bug." 20 | ); 21 | return; 22 | } 23 | } 24 | warn( 25 | "If you think this is a bug on redub, do test with dub, if it works, do file an issue at ", 26 | CreateIssueURL 27 | ); 28 | } 29 | 30 | string getLatestVersion() 31 | { 32 | import std.net.curl; 33 | import hipjson; 34 | static string newestVer; 35 | if(!newestVer) 36 | { 37 | char[] tagsContent = get(GithubTagAPI); 38 | if(tagsContent.length == 0) 39 | return null; 40 | newestVer = parseJSON(cast(string)tagsContent).array[0]["name"].str; 41 | } 42 | return newestVer; 43 | } 44 | 45 | string getRedubDownloadLink(string ver) 46 | { 47 | return "https://github.com/"~RedubUserRepository~"/archive/refs/tags/"~ver~".zip"; 48 | } 49 | 50 | string getCreateIssueURL(){return CreateIssueURL;} -------------------------------------------------------------------------------- /source/redub/misc/glob_entries.d: -------------------------------------------------------------------------------- 1 | module redub.misc.glob_entries; 2 | public import std.file:DirEntry; 3 | 4 | auto globDirEntriesShallow(string dirGlob) 5 | { 6 | import std.path; 7 | import redub.misc.path; 8 | import std.file; 9 | import std.traits:ReturnType; 10 | import std.string:indexOf; 11 | import std.stdio; 12 | 13 | 14 | struct ShallowGlob 15 | { 16 | string glob; 17 | typeof(dirEntries("", SpanMode.shallow)) entries; 18 | 19 | bool empty() 20 | { 21 | if(entries == entries.init) 22 | return glob.length == 0; 23 | return entries.empty; 24 | } 25 | void popFront() 26 | { 27 | if(entries == entries.init) 28 | return; 29 | entries.popFront; 30 | } 31 | DirEntry front() 32 | { 33 | if(entries == entries.init) 34 | { 35 | DirEntry ret = DirEntry(glob); 36 | glob = null; 37 | return ret; 38 | } 39 | while(!entries.front.name.globMatch(glob)) 40 | entries.popFront; 41 | return entries.front; 42 | } 43 | } 44 | 45 | ptrdiff_t idx = indexOf(dirGlob, '*'); 46 | if(idx == -1) 47 | return ShallowGlob(dirGlob); 48 | 49 | import std.exception : enforce; 50 | enforce(indexOf(dirGlob[idx+1..$], '*') == -1, "Only shallow dir entries can be used from that function, received " ~ dirGlob); 51 | string initialPath = redub.misc.path.buildNormalizedPath(dirGlob[0..idx]); 52 | string theGlob = dirGlob[idx..$]; 53 | enforce(isDir(initialPath), "Path "~initialPath~" is not a directory to iterate."); 54 | 55 | return ShallowGlob(theGlob, dirEntries(initialPath, SpanMode.shallow)); 56 | } -------------------------------------------------------------------------------- /source/redub/misc/hard_link.d: -------------------------------------------------------------------------------- 1 | module redub.misc.hard_link; 2 | 3 | 4 | version(Windows) 5 | extern(Windows) int CreateHardLinkW(const(wchar)* to, const(wchar)* from, void* secAttributes); 6 | 7 | private bool isSameFile(string a, string b) 8 | { 9 | import std.file; 10 | DirEntry aDir = DirEntry(a); 11 | DirEntry bDir = DirEntry(b); 12 | version(Posix) 13 | { 14 | return aDir.statBuf == bDir.statBuf; 15 | } 16 | else 17 | { 18 | return aDir.isDir == bDir.isDir && 19 | aDir.timeLastModified == bDir.timeLastModified && 20 | aDir.size == bDir.size && 21 | aDir.isSymlink == bDir.isSymlink; 22 | } 23 | } 24 | 25 | 26 | bool hardLinkFile(string from, string to, bool overwrite = false) 27 | { 28 | import redub.logging; 29 | import std.exception; 30 | import std.file; 31 | import std.path; 32 | import std.utf; 33 | if(!exists(from)) 34 | throw new Exception("File "~from~ " does not exists. "); 35 | if(!isFile(from)) 36 | throw new Exception("Input '"~from~"' is not a file. "); 37 | string toDir = dirName(to); 38 | if(!exists(toDir)) 39 | { 40 | throw new Exception("The output directory '"~toDir~"' from the copy operation with input file '"~from~"' does not exists."); 41 | } 42 | if (exists(to)) 43 | { 44 | enforce(overwrite, "Destination file already exists."); 45 | if(isSameFile(from, to)) 46 | return true; 47 | } 48 | uint attr = DirEntry(from).attributes; 49 | static bool isWritable(uint attributes) 50 | { 51 | version(Windows) 52 | { 53 | enum FILE_ATTRIBUTE_READONLY = 0x01; 54 | return (attributes & FILE_ATTRIBUTE_READONLY) == 0; 55 | } 56 | else 57 | { 58 | import core.sys.posix.sys.stat : S_IWUSR, S_IWGRP, S_IWOTH; 59 | return (attributes & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0; 60 | } 61 | } 62 | 63 | const writeAccessChangeRequired = overwrite && !isWritable(attr); 64 | if (!writeAccessChangeRequired) 65 | { 66 | version (Windows) 67 | { 68 | alias cstr = toUTFz!(const(wchar)*); 69 | if(CreateHardLinkW(cstr(to), cstr(from), null) != 0) 70 | return true; 71 | } 72 | else 73 | { 74 | alias cstr = toUTFz!(const(char)*); 75 | import core.sys.posix.unistd : link; 76 | if(link(cstr(from), cstr(to)) == 0) 77 | return true; 78 | } 79 | } 80 | // fallback to copy 81 | try 82 | { 83 | std.file.copy(from, to); 84 | std.file.setAttributes(to, attr); 85 | } 86 | catch(Exception e) 87 | { 88 | errorTitle("Could not copy "~from, " " , e.toString()); 89 | return false; 90 | } 91 | return true; 92 | } 93 | 94 | bool hardLinkDir(string dir, string to, bool overwrite = false) 95 | { 96 | import std.exception; 97 | import redub.misc.path; 98 | import std.path; 99 | import std.file; 100 | 101 | foreach(DirEntry e; dirEntries(dir, SpanMode.depth)) 102 | { 103 | if(e.isDir) 104 | continue; 105 | string output = redub.misc.path.buildNormalizedPath(to, e.name[dir.length+1..$]); 106 | string outputDir = dirName(output); 107 | if(!exists(outputDir)) 108 | mkdirRecurse(outputDir); 109 | 110 | if(!hardLinkFile(e.name, output, overwrite)) 111 | return false; 112 | } 113 | return true; 114 | } 115 | 116 | bool hardLink(string input, string to, bool overwrite = false) 117 | { 118 | import std.file; 119 | if(isDir(input)) 120 | return hardLinkDir(input, to, overwrite); 121 | return hardLinkFile(input, to, overwrite); 122 | } -------------------------------------------------------------------------------- /source/redub/misc/ldc_conf_parser.d: -------------------------------------------------------------------------------- 1 | module redub.misc.ldc_conf_parser; 2 | 3 | 4 | struct ConfigSection 5 | { 6 | /// Name of the config section. 7 | string name; 8 | /// Maps keys to values (only string lists and strings supported) 9 | string[string] values; 10 | } 11 | 12 | ConfigSection getLdcConfig(string cwd, string ldcBinPath, string triple) 13 | { 14 | import std.file:readText; 15 | import std.algorithm.searching; 16 | if(triple is null) 17 | triple = "default"; 18 | string confPath = getLdcConfPath(cwd,ldcBinPath); 19 | if(confPath is null) 20 | return ConfigSection.init; 21 | return parseLDCConfig(readText(confPath), triple); 22 | } 23 | 24 | 25 | /** 26 | * Finds the ldc2.conf file, basing itself on "Configuration File" section at https://wiki.dlang.org/Using_LDC 27 | * Params: 28 | * cwd = Current Working Dir 29 | * ldcBinPath = LDC path, ending with /bin 30 | * Returns: Path where ldc2.conf is located first 31 | */ 32 | private string getLdcConfPath(string cwd, string ldcBinPath) 33 | { 34 | import std.path; 35 | import std.array; 36 | import std.process; 37 | import std.system; 38 | import redub.command_generators.commons; 39 | static string ldc2InPath(string basePath) 40 | { 41 | import std.file; 42 | string p = buildNormalizedPath(basePath, "ldc2.conf"); 43 | if(exists(p)) 44 | return p; 45 | return null; 46 | } 47 | auto testPaths = [ 48 | cwd, 49 | dirName(ldcBinPath), 50 | os.isWindows ? buildNormalizedPath(environment["APPDATA"], ".ldc") : "~/.ldc", 51 | os.isWindows ? environment["APPDATA"] : null, 52 | buildNormalizedPath(dirName(ldcBinPath), "..", "etc"), 53 | //6. and 7. not implemented since I didn't understand what is supposed to mean 54 | os.isPosix ? "/etc" : null, 55 | os.isPosix ? "/etc/ldc" : null 56 | ].staticArray; 57 | 58 | foreach(t; testPaths) 59 | { 60 | if(t is null) 61 | continue; 62 | string ret = ldc2InPath(t); 63 | if(ret) return ret; 64 | } 65 | return null; 66 | } 67 | 68 | 69 | ConfigSection parseLDCConfig(string configText, string confToMatch) 70 | { 71 | import redub.misc.mini_regex; 72 | import std.string; 73 | import std.algorithm.searching; 74 | if(confToMatch.length == 0) 75 | confToMatch = "default"; 76 | 77 | ConfigSection currentSection; 78 | bool foundSection; 79 | 80 | string lastKey; 81 | string valueBeingParsed; 82 | 83 | foreach (line; lineSplitter(configText)) 84 | { 85 | line = line.strip; // Remove leading/trailing spaces 86 | if (line.empty || line.startsWith("//")) continue; // Skip empty lines and comments 87 | 88 | if (!foundSection && line.endsWith(":")) //New section 89 | { 90 | string sectionName = line[0 .. $ - 1].strip; 91 | if(sectionName.length && sectionName[0] == '"') 92 | sectionName = sectionName[1..$-1]; 93 | if(matches(confToMatch, sectionName)) //If it didn't match the regex, continue 94 | { 95 | foundSection = true; 96 | currentSection.name = sectionName; 97 | } 98 | } 99 | else if(!foundSection) 100 | continue; 101 | else if (valueBeingParsed.length != 0) 102 | { 103 | if(line.endsWith(";")) 104 | { 105 | valueBeingParsed~= line[0..$-1]; 106 | currentSection.values[lastKey] = valueBeingParsed; 107 | lastKey = valueBeingParsed = null; 108 | } 109 | else 110 | valueBeingParsed~= line; 111 | 112 | } 113 | else if (foundSection && line.canFind("=")) 114 | { 115 | // Parse key-value pair 116 | auto parts = line.split("="); 117 | string key = parts[0].strip; 118 | string value = parts[1].strip; 119 | lastKey = key; 120 | valueBeingParsed = value; 121 | 122 | if (value.endsWith(";")) 123 | { 124 | currentSection.values[key] = value[0..$-1]; 125 | lastKey = valueBeingParsed = null; 126 | } 127 | } 128 | } 129 | return currentSection; 130 | } 131 | 132 | 133 | unittest 134 | { 135 | string ldc2example = q"EOS 136 | // See comments in driver/config.d in ldc source tree for grammar description of 137 | // this config file. 138 | 139 | // For cross-compilation, you can add sections for specific target triples by 140 | // naming the sections as (quoted) regex patterns. See LDC's `-v` output 141 | // (`config` line) to figure out your normalized triple, depending on the used 142 | // `-mtriple`, `-m32` etc. E.g.: 143 | // 144 | // "^arm.*-linux-gnueabihf$": { … }; 145 | // "86(_64)?-.*-linux": { … }; 146 | // "i[3-6]86-.*-windows-msvc": { … }; 147 | // 148 | // Later sections take precedence and override settings from previous matching 149 | // sections while inheriting unspecified settings from previous sections. 150 | // A `default` section always matches (treated as ".*") and is therefore usually 151 | // the first section. 152 | default: 153 | { 154 | // default switches injected before all explicit command-line switches 155 | switches = [ 156 | "-defaultlib=phobos2-ldc,druntime-ldc", 157 | ]; 158 | // default switches appended after all explicit command-line switches 159 | post-switches = [ 160 | "-I%%ldcbinarypath%%/../import", 161 | ]; 162 | // default directories to be searched for libraries when linking 163 | lib-dirs = [ 164 | "%%ldcbinarypath%%/../lib", 165 | ]; 166 | // default rpath when linking against the shared default libs 167 | rpath = "%%ldcbinarypath%%/../lib"; 168 | }; 169 | 170 | "^wasm(32|64)-": 171 | { 172 | switches = [ 173 | "-defaultlib=", 174 | "-L-z", "-Lstack-size=1048576", 175 | "-L--stack-first", 176 | "-link-internally", 177 | "-L--export-dynamic", 178 | ]; 179 | lib-dirs = []; 180 | }; 181 | 182 | "i686-.*-linux-gnu": 183 | { 184 | lib-dirs = [ 185 | "%%ldcbinarypath%%/../lib32", 186 | ]; 187 | rpath = "%%ldcbinarypath%%/../lib32"; 188 | }; 189 | EOS"; 190 | 191 | assert(parseLDCConfig(ldc2example, "wasm32-unknown-unknown").values["switches"].length); 192 | assert(parseLDCConfig(ldc2example, "").values["rpath"].length); 193 | 194 | } -------------------------------------------------------------------------------- /source/redub/misc/mini_regex.d: -------------------------------------------------------------------------------- 1 | module redub.misc.mini_regex; 2 | 3 | 4 | bool matches(string input, string rlike) 5 | { 6 | if(rlike.length == 0) 7 | return true; 8 | 9 | import std.string; 10 | import std.exception; 11 | static enum RegType 12 | { 13 | range, 14 | characters, 15 | matchAll, 16 | multiMatch 17 | } 18 | static struct RegAlgo 19 | { 20 | int delegate(string) fn; 21 | RegType type; 22 | string characters; 23 | bool optional; 24 | ///Keep that algorithm while it is matching 25 | bool greedy; 26 | 27 | 28 | ptrdiff_t matches(string input) 29 | { 30 | if(type == RegType.characters) 31 | return input == this.characters ? characters.length : -1; 32 | if(type == RegType.matchAll) 33 | return 1; 34 | ptrdiff_t ret = fn(input); 35 | if(optional && ret == -1) 36 | return 0; 37 | return ret; 38 | } 39 | } 40 | RegAlgo[] builtRegexMatch; 41 | int regIndex; 42 | 43 | 44 | 45 | static int delegate(string) inRange(char left, char right) 46 | { 47 | return (string input) 48 | { 49 | return (input[0] >= left && input[0] <= right) ? 1 : -1; 50 | }; 51 | } 52 | 53 | static int delegate(string) getParenthesisHandling(string parenthesisExp) 54 | { 55 | import std.string:split; 56 | string[] references = parenthesisExp.split("|"); 57 | return (string input) 58 | { 59 | foreach(r; references) 60 | { 61 | if(input == r) 62 | return cast(int)input.length; 63 | } 64 | return -1; 65 | }; 66 | } 67 | 68 | ptrdiff_t startCharIndex = -1; 69 | for(ptrdiff_t i = 0; i < rlike.length; i++) 70 | { 71 | char ch = rlike[i]; 72 | if(ch == '^' || ch == '$') continue; 73 | if(startCharIndex == -1) 74 | startCharIndex = i; 75 | bool found = true; 76 | ptrdiff_t currI = i; 77 | switch(rlike[i]) 78 | { 79 | case '.': 80 | builtRegexMatch~= RegAlgo(null, RegType.matchAll); 81 | break; 82 | case '*': 83 | enforce(builtRegexMatch.length > 0, "Can't start a mini regex with a greedy optional '*'"~rlike); 84 | builtRegexMatch[$-1].greedy = true; 85 | builtRegexMatch[$-1].optional = true; 86 | break; 87 | case '?': 88 | enforce(builtRegexMatch.length > 0, "Can't start a mini regex with a wildcard '?'"~rlike); 89 | enforce(!builtRegexMatch[$-1].optional, "Can't double set as optional (??) on mini regex "~rlike); 90 | builtRegexMatch[$-1].optional = true; 91 | break; 92 | case '[': 93 | ptrdiff_t n = indexOf(rlike, ']', i); 94 | enforce(n != -1, "End of range ']' not found on "~rlike); 95 | string[] splitted = split(rlike[i+1..n], '-'); 96 | enforce(splitted.length == 2, "Range is not separated by a '-' at "~rlike); 97 | char left = splitted[0][0], right = splitted[1][0]; 98 | 99 | enforce(right > left, "Can't create a range where right parameter '"~right~"' is not bigger than left '"~left~"'"); 100 | 101 | builtRegexMatch~= RegAlgo(inRange(splitted[0][0], splitted[1][0]), RegType.range); 102 | i = n; 103 | break; 104 | case '(': 105 | ptrdiff_t n = indexOf(rlike, ')', i); 106 | enforce(n != -1, "End of multiple match character ')' not found on "~rlike); 107 | builtRegexMatch~= RegAlgo(getParenthesisHandling(rlike[i+1..n]), RegType.multiMatch); 108 | i = n; 109 | break; 110 | default: 111 | found = false; 112 | break; 113 | } 114 | if(found) 115 | { 116 | if(startCharIndex != currI) 117 | { 118 | RegAlgo temp = builtRegexMatch[$-1]; 119 | builtRegexMatch[$-1] = RegAlgo(null, RegType.characters, rlike[startCharIndex..currI]); 120 | builtRegexMatch~= temp; 121 | } 122 | startCharIndex = -1; 123 | } 124 | } 125 | if(startCharIndex != -1 && startCharIndex < rlike.length) 126 | builtRegexMatch~= RegAlgo(null, RegType.characters, rlike[startCharIndex..rlike.length]); 127 | 128 | 129 | int lastMatchStart = 0; 130 | for(int i = 0; i <= input.length; i++) 131 | { 132 | import std.stdio; 133 | string temp = input[lastMatchStart..i]; 134 | RegAlgo a = builtRegexMatch[regIndex]; 135 | if(a.characters && temp.length > a.characters.length) 136 | return false; 137 | 138 | if(a.greedy && a.type == RegType.matchAll && a.optional) 139 | { 140 | if(regIndex == builtRegexMatch.length - 1) 141 | return true; 142 | RegAlgo next = builtRegexMatch[regIndex+1]; 143 | 144 | ptrdiff_t nextRes = -1; 145 | ptrdiff_t start = i; 146 | while(i < input.length && nextRes == -1) 147 | { 148 | i++; 149 | string futureInput = input[start..i]; 150 | if(next.type == RegType.characters && futureInput.length > next.characters.length) 151 | futureInput = input[++start..i]; 152 | nextRes = next.matches(futureInput); 153 | // writeln("Testing ", futureInput, " against ", next.characters); 154 | } 155 | if(nextRes != -1) 156 | { 157 | // writeln("Matched: ", next.characters, " curret index ", i); 158 | regIndex+= 2; 159 | continue; 160 | } 161 | 162 | } 163 | 164 | ptrdiff_t res = a.matches(temp); 165 | 166 | // writeln("Testing ", temp, " against ", builtRegexMatch[regIndex].type, ": ", res); 167 | if(res != -1) 168 | { 169 | lastMatchStart = i; 170 | regIndex++; 171 | if(regIndex == builtRegexMatch.length) 172 | return true; 173 | } 174 | } 175 | 176 | return regIndex == builtRegexMatch.length; 177 | } 178 | 179 | unittest 180 | { 181 | assert(matches("default", "default")); 182 | assert(matches("wasm32-unknown-unknown", "^wasm(32|64)-")); 183 | assert(matches("i686-unknown-windows-msvc", "i[3-6]86-.*-windows-msvc")); 184 | assert(matches("arm-none-linux-gnueabihf", "arm.*-linux-gnueabihf")); 185 | 186 | } -------------------------------------------------------------------------------- /source/redub/misc/path.d: -------------------------------------------------------------------------------- 1 | module redub.misc.path; 2 | 3 | string buildNormalizedPath(scope string[] paths...) 4 | { 5 | char[] buffer; 6 | string output = normalizePath(buffer, paths); 7 | return output; 8 | } 9 | 10 | string normalizePath(return ref char[] output, scope string[] paths...) 11 | { 12 | size_t start, length; 13 | import std.ascii; 14 | static string[1024] normalized; 15 | 16 | foreach(path; paths) 17 | { 18 | foreach(p; pathSplitterRange(path)) 19 | { 20 | if(p == ".") 21 | continue; 22 | else if(p == "..") 23 | { 24 | if(length > 0) 25 | length--; 26 | else 27 | start++; 28 | } 29 | else 30 | { 31 | version(Posix) 32 | { 33 | if(p.length == 0) //Path is a single slash 34 | length = start = 0; 35 | } 36 | else 37 | { 38 | if(p.length > 1 && p[1] == ':') //Path has drive letter is absolute 39 | length = start = 0; 40 | } 41 | normalized[length++] = p; 42 | } 43 | } 44 | } 45 | import core.memory; 46 | if(length == 1) 47 | { 48 | if(output.length == 0) 49 | output = normalized[0].dup; 50 | else 51 | output[0..normalized[0].length] = normalized[0]; 52 | return cast(string)output[0..normalized[0].length]; 53 | } 54 | 55 | size_t totalLength = (length - start) - 1; 56 | for(int i = cast(int)start; i < length; i++) 57 | totalLength+= normalized[i].length; 58 | 59 | if(output.length == 0) 60 | output = (cast(char*)GC.malloc(totalLength, GC.BlkAttr.NO_SCAN))[0..totalLength]; 61 | 62 | totalLength = 0; 63 | for(int i = cast(int)start; i < length; i++) 64 | { 65 | output[totalLength..totalLength+normalized[i].length] = normalized[i]; 66 | totalLength+= normalized[i].length; 67 | if(i + 1 < length) 68 | output[totalLength++] = pathSeparator; 69 | } 70 | 71 | return cast(string)output[0..totalLength]; 72 | } 73 | 74 | 75 | 76 | 77 | auto pathSplitterRange(string path) pure @safe nothrow @nogc 78 | { 79 | struct PathRange 80 | { 81 | string path; 82 | size_t indexRight = 0; 83 | 84 | bool empty() @safe pure nothrow @nogc {return indexRight >= path.length;} 85 | bool hasNext() @safe pure nothrow @nogc {return indexRight + 1 < path.length;} 86 | string front() @safe pure nothrow @nogc 87 | { 88 | size_t i = indexRight; 89 | while(i < path.length && path[i] != '\\' && path[i] != '/') 90 | i++; 91 | indexRight = i; 92 | return path[0..indexRight]; 93 | } 94 | void popFront() @safe pure nothrow @nogc 95 | { 96 | if(indexRight+1 < path.length) 97 | { 98 | path = path[indexRight+1..$]; 99 | indexRight = 0; 100 | } 101 | else 102 | indexRight+= 1; //Guarantees empty 103 | } 104 | } 105 | 106 | return PathRange(path); 107 | } 108 | version(Windows) 109 | { 110 | enum pathSeparator = '\\'; 111 | enum otherSeparator = '/'; 112 | } 113 | else 114 | { 115 | enum pathSeparator = '/'; 116 | enum otherSeparator = '\\'; 117 | } -------------------------------------------------------------------------------- /source/redub/package_searching/api.d: -------------------------------------------------------------------------------- 1 | module redub.package_searching.api; 2 | public import redub.libs.semver; 3 | 4 | 5 | struct PackageInfo 6 | { 7 | string packageName; 8 | string subPackage; 9 | SemVer requiredVersion; 10 | SemVer bestVersion; 11 | string path; 12 | string requiredBy; 13 | } 14 | 15 | struct ReducedPackageInfo 16 | { 17 | string bestVersion; 18 | string bestVersionPath; 19 | SemVer[] foundVersions; 20 | } 21 | 22 | 23 | 24 | /** 25 | * Separates the subpackage name from the entire dependency name. 26 | * Params: 27 | * packageName = A package name format, such as redub:adv_diff 28 | * mainPackageName = In the case of redub:adv_diff, it will return redub 29 | * Returns: The subpackage name. In case of redub:adv_diff, returns adv_diff. 30 | */ 31 | private string getSubPackageInfo(string packageName, out string mainPackageName) 32 | { 33 | import std.string : indexOf; 34 | 35 | ptrdiff_t ind = packageName.indexOf(":"); 36 | if (ind == -1) 37 | { 38 | mainPackageName = packageName; 39 | return null; 40 | } 41 | mainPackageName = packageName[0 .. ind]; 42 | return packageName[ind + 1 .. $]; 43 | } 44 | 45 | /** 46 | * Gets the full package name. Used for caching 47 | * Params: 48 | * packageName = The package name specification 49 | * requiredBy = Which is requesting it 50 | * Returns: The package full name. For example - (:gl, renderer) returns 'renderer:gl' 51 | */ 52 | string getPackageFullName(string packageName, string requiredBy) 53 | { 54 | if(packageName.length && packageName[0] == ':') 55 | return requiredBy~packageName; 56 | return packageName; 57 | } 58 | 59 | 60 | /** 61 | * Same as getSubPackageInfo, but infer mainPackageName in case of sending a subPackage only, such as :adv_diff 62 | * Params: 63 | * packageName = The package dependency specification 64 | * requiredBy = The requiredBy may be used in case of internal dependency specification, such as :adv_diff 65 | * mainPackageName = The separated main package name from the sub package 66 | * Returns: The subpackage 67 | */ 68 | string getSubPackageInfoRequiredBy(string packageName, string requiredBy, out string mainPackageName) 69 | { 70 | string sub = getSubPackageInfo(packageName, mainPackageName); 71 | if(sub.length && mainPackageName.length == 0) 72 | mainPackageName = requiredBy; 73 | return sub; 74 | } 75 | 76 | 77 | PackageInfo basePackage(string packageName, string packageVersion, string requiredBy) 78 | { 79 | PackageInfo pack; 80 | pack.subPackage = getSubPackageInfoRequiredBy(packageName, requiredBy, pack.packageName); 81 | pack.requiredBy = requiredBy; 82 | pack.requiredVersion = SemVer(packageVersion); 83 | return pack; 84 | } -------------------------------------------------------------------------------- /source/redub/package_searching/cache.d: -------------------------------------------------------------------------------- 1 | module redub.package_searching.cache; 2 | import core.sync.mutex; 3 | import core.attribute; 4 | public import redub.package_searching.api; 5 | 6 | 7 | /** 8 | * The packages cache is a package list indexed by their name. 9 | * This list contains different versions of the package inside it. 10 | * Those different versions are used to identify the best compatible version among them. 11 | */ 12 | private __gshared PackageInfo[string] packagesCache; 13 | private __gshared Mutex cacheMtx; 14 | 15 | @standalone @trusted 16 | shared static this() 17 | { 18 | cacheMtx = new Mutex; 19 | } 20 | 21 | /** 22 | * 23 | * Params: 24 | * packageName = The package name to find 25 | * repo = Repo information that is only ever used whenever the version is invalid 26 | * packageVersion = The version of the package, may be both a SemVer or a branch 27 | * requiredBy = Metadata information 28 | * path = The path on which this package may be. Used whenever not in the cache 29 | * Returns: The package information 30 | */ 31 | PackageInfo* findPackage(string packageName, string repo, string packageVersion, string requiredBy, string path) 32 | { 33 | PackageInfo* pkg = packageName in packagesCache; 34 | if(pkg) return pkg; 35 | if(path.length == 0) return findPackage(packageName, repo, packageVersion, requiredBy); 36 | 37 | PackageInfo localPackage = basePackage(packageName, packageVersion, requiredBy); 38 | localPackage.path = path; 39 | synchronized(cacheMtx) 40 | { 41 | packagesCache[packageName] = localPackage; 42 | } 43 | return packageName in packagesCache; 44 | } 45 | 46 | private string getBetterPackageInfo(PackageInfo* input, string packageName) 47 | { 48 | string ret = packageName; 49 | while(input != null) 50 | { 51 | if(input.requiredBy == null) 52 | break; 53 | ret = input.requiredBy~"->"~ret; 54 | input = input.requiredBy in packagesCache; 55 | } 56 | return ret; 57 | } 58 | 59 | /** 60 | * 61 | * Params: 62 | * packageName = The package name to find 63 | * repo = The repo information. Used when the package version is invalid. Information only used to fetch 64 | * packageVersion = The SemVer version to look for. Or a git branch 65 | * requiredBy = Meta information on whom is requiring it. 66 | * Returns: 67 | */ 68 | PackageInfo* findPackage(string packageName, string repo, string packageVersion, string requiredBy) 69 | { 70 | import redub.package_searching.dub; 71 | PackageInfo* pkg = packageName in packagesCache; 72 | if(!pkg) 73 | { 74 | PackageInfo info = getPackage(packageName, repo, packageVersion, requiredBy); 75 | synchronized(cacheMtx) 76 | { 77 | packagesCache[packageName] = info; 78 | } 79 | } 80 | else 81 | { 82 | import redub.logging; 83 | SemVer newPkg = SemVer(packageVersion); 84 | if(!pkg.bestVersion.satisfies(newPkg)) 85 | { 86 | if(newPkg.satisfies(pkg.requiredVersion)) 87 | { 88 | PackageInfo newPkgInfo = getPackage(packageName, repo, packageVersion, requiredBy); 89 | pkg.bestVersion = newPkgInfo.bestVersion; 90 | pkg.path = newPkgInfo.path; 91 | import redub.logging; 92 | error("Using ", pkg.path, " for package ", pkg.packageName); 93 | } 94 | else 95 | throw new Exception("Package "~packageName~" with first requirement found '"~pkg.requiredVersion.toString~"' from the dependency '" ~ 96 | getBetterPackageInfo(pkg, packageName)~"' is not compatible with the new requirement: "~packageVersion ~ " required by "~requiredBy~ " ("~getBetterPackageInfo(requiredBy in packagesCache, requiredBy)~")"); 97 | } 98 | vlog("Using ", packageName, " with version: ", pkg.bestVersion, ". Initial requirement was '", pkg.requiredVersion, ". Current is ", packageVersion); 99 | return pkg; 100 | } 101 | return packageName in packagesCache; 102 | } 103 | 104 | 105 | /** 106 | * 107 | * Params: 108 | * packageName = What is the root package name 109 | * path = Where the root package is located. 110 | */ 111 | void putRootPackageInCache(string packageName, string path) 112 | { 113 | synchronized(cacheMtx) 114 | { 115 | packagesCache[packageName] = PackageInfo(packageName, null, SemVer(0,0,0), SemVer(0,0,0), path, null,); 116 | } 117 | } 118 | 119 | /** 120 | * Puts the package in cache. This is only called from subPackage, and, when this package is searched again, redub will know where to look at. 121 | * Params: 122 | * packageName = The full package name 123 | * version_ = Which version is it 124 | * path = Where is it located 125 | */ 126 | void putPackageInCache(string packageName, string version_, string path) 127 | { 128 | synchronized(cacheMtx) 129 | { 130 | packagesCache[packageName] = PackageInfo(packageName, null, SemVer(version_), SemVer(version_), path, null,); 131 | } 132 | } 133 | 134 | void clearPackageCache() 135 | { 136 | synchronized(cacheMtx) 137 | packagesCache = null; 138 | } 139 | 140 | string getPackagePath(string packageName, string repo, string packageVersion, string requiredBy) 141 | { 142 | return findPackage(packageName, repo ,packageVersion, requiredBy).path; 143 | } 144 | -------------------------------------------------------------------------------- /source/redub/package_searching/downloader.d: -------------------------------------------------------------------------------- 1 | module redub.package_searching.downloader; 2 | import redub.libs.package_suppliers.dub_registry; 3 | import redub.libs.semver; 4 | import core.sync.mutex; 5 | RegistryPackageSupplier supplier; 6 | 7 | string getGitDownloadLink(string packageName, string repo, string branch) 8 | { 9 | import std.algorithm.searching; 10 | import std.uri; 11 | if(repo.startsWith("git+")) 12 | repo = repo[4..$]; 13 | string downloadLink; 14 | if(repo[$-1] == '/') 15 | repo = repo[0..$-1]; 16 | 17 | if(repo.canFind("gitlab.com")) 18 | downloadLink = repo~"/-/archive/"~branch~"/"~encodeComponent(packageName)~"-"~branch~".zip"; 19 | else if(repo.canFind("bitbucket.com")) 20 | downloadLink = repo~"/get/"~encodeComponent(packageName)~"-"~branch~".zip"; 21 | else 22 | { 23 | ///Github, Gitea and GitBucket all use the same style 24 | downloadLink = repo~"/archive/"~branch~".zip"; 25 | } 26 | return downloadLink; 27 | } 28 | 29 | string getDownloadLink(string packageName, string repo, SemVer requirement, out SemVer actualVer) 30 | { 31 | if(requirement.isInvalid) 32 | { 33 | if(!repo) 34 | throw new Exception("Can't have invalid requirement '"~requirement.toString~"' and have no 'repo' information."); 35 | actualVer = requirement; 36 | return getGitDownloadLink(packageName, repo, requirement.toString); 37 | } 38 | return supplier.getBestPackageDownloadUrl(packageName, requirement, actualVer); 39 | } 40 | 41 | /** 42 | * Downloads a .zip containing a package with the specified requirement 43 | * 44 | * Params: 45 | * packageName = The package name to find 46 | * repo = Null whenever a valid requirement exists. An invalid SemVer is used for git branches 47 | * requirement = Required. When valid, uses dub registry, when invalid, uses git repo 48 | * out_actualVersion = Actual version is for the best package found. Only relevant when a valid SemVer is in place 49 | * url = Actual URL from which the download was made 50 | * Returns: The downloaded content 51 | */ 52 | ubyte[] fetchPackage(string packageName, string repo, SemVer requirement, out SemVer out_actualVersion, out string url) 53 | { 54 | import redub.libs.package_suppliers.utils; 55 | url = getDownloadLink(packageName, repo, requirement, out_actualVersion); 56 | if(!url) 57 | return null; 58 | return downloadFile(url); 59 | } 60 | 61 | /** 62 | * Downloads the package to a specific folder and extract it. 63 | * If no version actually matched existing versions, both out_actualVersionRequirement and url will be empty 64 | * 65 | * Params: 66 | * path = The path expected to extract the package to 67 | * repo = Required when an invalid semver is sent. 68 | * packageName = Package name for assembling the link 69 | * requirement = Which is the requirement that it must attend 70 | * out_actualVersion = The version that matched the required 71 | * url = The URL that was built for downloading the package 72 | * Returns: The path after the extraction 73 | */ 74 | string downloadPackageTo(return string path, string packageName, string repo, SemVer requirement, out SemVer out_actualVersion, out string url) 75 | { 76 | import redub.libs.package_suppliers.utils; 77 | ubyte[] zipContent = fetchPackage(packageName, repo, requirement, out_actualVersion, url); 78 | if(!zipContent) 79 | return null; 80 | if(!extractZipToFolder(zipContent, path)) 81 | throw new Exception("Error while trying to extract zip to path "~path); 82 | return path; 83 | } 84 | 85 | 86 | ///Uses that mutex for printing and managing downloader cache, thus, avoiding also more than a single package to be downloaded twice 87 | private __gshared Mutex downloaderMutex; 88 | 89 | /** 90 | * Dub downloads to a path, usually packagename-version 91 | * After that, it replaces it to packagename/version 92 | * Then, it will load the .sdl or .json file 93 | * If it uses .sdl, convert it to .json and add a version to it 94 | * If it uses .json, append a "version" to it 95 | */ 96 | string downloadPackageTo(return string path, string packageName, string repo, SemVer requirement, out SemVer actualVersion) 97 | { 98 | import core.thread; 99 | import core.sync.mutex; 100 | import core.sync.condition; 101 | import hipjson; 102 | import redub.libs.package_suppliers.utils; 103 | import std.path; 104 | import std.file; 105 | import redub.logging; 106 | //Supplier will download packageName-version. While dub actually creates packagename/version/ 107 | string url; 108 | 109 | struct DownloadData 110 | { 111 | Mutex mtx; 112 | string ver; 113 | } 114 | 115 | 116 | ///Create a temporary directory for outputting downloaded version. This will ensure a single version is found on getFirstFile 117 | string tempPath = buildNormalizedPath(path, "temp"); 118 | size_t timeout = 10_000; 119 | __gshared DownloadData[string] downloadedPackages; 120 | 121 | 122 | bool willDownload; 123 | 124 | synchronized(downloaderMutex) 125 | { 126 | if((packageName in downloadedPackages) is null) 127 | { 128 | downloadedPackages[packageName] = DownloadData(new Mutex, null); 129 | willDownload = true; 130 | } 131 | } 132 | downloadedPackages[packageName].mtx.lock; 133 | 134 | scope(exit) 135 | { 136 | if(std.file.exists(tempPath)) 137 | std.file.rmdirRecurse((tempPath)); 138 | downloadedPackages[packageName].mtx.unlock; 139 | } 140 | 141 | 142 | while(std.file.exists(tempPath)) 143 | { 144 | ///If exists a temporary path at that same directory, simply wait until it is removed 145 | import core.time; 146 | Thread.sleep(dur!"msecs"(50)); 147 | timeout-= 50; 148 | if(timeout == 0) 149 | { 150 | errorTitle("Redub Fetch Timeout: ", "Wait time of 10 seconds for package "~packageName~" temp folder to be removed has been exceeded"); 151 | throw new NetworkException("Timeout while waiting for removing "~tempPath); 152 | } 153 | } 154 | 155 | 156 | 157 | if(!willDownload) 158 | { 159 | return getOutputDirectoryForPackage(path, packageName, downloadedPackages[packageName].ver); 160 | } 161 | else 162 | { 163 | synchronized(downloaderMutex) 164 | { 165 | warnTitle("Fetching Package: ", packageName, " ", repo, " version ", requirement.toString); 166 | import std.stdio; 167 | stdout.flush; 168 | } 169 | tempPath = downloadPackageTo(tempPath, packageName, repo, requirement, actualVersion, url); 170 | if(!url) 171 | { 172 | import redub.libs.semver; 173 | string existing = "'. Existing Versions: "; 174 | SemVer[] vers = supplier.getExistingVersions(packageName); 175 | if(vers is null) existing = "'. This package does not exists in the registry."; 176 | else 177 | foreach(v; vers) existing~= "\n\t"~v.toString; 178 | 179 | 180 | throw new NetworkException("No version with requirement '"~requirement.toString~"' was found when looking for package "~packageName~existing); 181 | } 182 | synchronized(downloaderMutex) 183 | { 184 | downloadedPackages[packageName].ver = actualVersion.toString; 185 | } 186 | path = getOutputDirectoryForPackage(path, packageName, actualVersion.toString); 187 | string installPath = getFirstFileInDirectory(tempPath); 188 | if(!installPath) 189 | throw new Exception("No file was extracted to directory "~tempPath~" while extracting package "~packageName); 190 | ///The download might have happen while downloading ourselves 191 | 192 | 193 | if(std.file.exists(path)) 194 | return path; 195 | 196 | string outputDir = dirName(path); 197 | if(!std.file.exists(outputDir)) 198 | mkdirRecurse(outputDir); 199 | rename(installPath, path); 200 | rmdirRecurse(tempPath); 201 | } 202 | 203 | 204 | 205 | 206 | string sdlPath = buildNormalizedPath(path, "dub.sdl"); 207 | string jsonPath = buildNormalizedPath(path, "dub.json"); 208 | JSONValue json; 209 | if(std.file.exists(sdlPath)) 210 | { 211 | import dub_sdl_to_json; 212 | try 213 | { 214 | json = sdlToJSON(parseSDL(sdlPath)); 215 | } 216 | catch(Exception e) 217 | { 218 | errorTitle("Could not convert SDL->JSON The package '"~packageName~"'. Exception\n"~e.msg); 219 | throw e; 220 | } 221 | std.file.remove(sdlPath); 222 | } 223 | else 224 | { 225 | if(!std.file.exists(jsonPath)) 226 | throw new NetworkException("Downloaded a dub package which has no dub configuration?"); 227 | json = parseJSON(std.file.readText(jsonPath)); 228 | } 229 | if(json.hasErrorOccurred) 230 | throw new Exception("Redub Json Parsing Error while reading json '"~jsonPath~"': "~json.error); 231 | 232 | //Inject version as `dub` itself requires that 233 | json["version"] = actualVersion.toString; 234 | std.file.write( 235 | jsonPath, 236 | json.toString, 237 | ); 238 | return path; 239 | } 240 | 241 | string getOutputDirectoryForPackage(string baseDir, string packageName, string packageVersion) 242 | { 243 | import std.path; 244 | if(packageVersion is null) 245 | throw new Exception("Can't create output directory for package "~ packageName~" because its version is null."); 246 | return buildNormalizedPath(baseDir, packageVersion, packageName); 247 | } 248 | 249 | static this() 250 | { 251 | supplier = new RegistryPackageSupplier(); 252 | } 253 | 254 | shared static this() 255 | { 256 | downloaderMutex = new Mutex; 257 | } 258 | -------------------------------------------------------------------------------- /source/redub/package_searching/dub.d: -------------------------------------------------------------------------------- 1 | module redub.package_searching.dub; 2 | import redub.package_searching.api; 3 | import redub.logging; 4 | import redub.api; 5 | import hipjson; 6 | import core.sync.mutex; 7 | 8 | 9 | 10 | struct FetchedPackage 11 | { 12 | string name; 13 | string reqBy; 14 | string version_; 15 | } 16 | 17 | __gshared FetchedPackage[] fetchedPackages; 18 | /** 19 | * 20 | * Params: 21 | * packageName = The package in which will be looked for 22 | * repo = Optional repository, used when an invalid version is sent. 23 | * packageVersion = Package version my be both a branch or a valid semver 24 | * requiredBy = Metadata info 25 | * Returns: Package information 26 | */ 27 | ReducedPackageInfo redubDownloadPackage(string packageName, string repo, string packageVersion, string requiredBy = "") 28 | { 29 | import redub.package_searching.downloader; 30 | import core.sync.mutex; 31 | import redub.misc.path; 32 | SemVer out_bestVersion; 33 | string downloadedPackagePath = downloadPackageTo( 34 | redub.misc.path.buildNormalizedPath(getDefaultLookupPathForPackages(), packageName), 35 | packageName, 36 | repo, 37 | SemVer(packageVersion), 38 | out_bestVersion 39 | ); 40 | 41 | synchronized 42 | { 43 | import std.algorithm.searching; 44 | if(!canFind!("a.name == b.name")(fetchedPackages, FetchedPackage(packageName))) 45 | fetchedPackages~= FetchedPackage(packageName, requiredBy, out_bestVersion.toString); 46 | } 47 | 48 | return ReducedPackageInfo( 49 | out_bestVersion.toString, 50 | downloadedPackagePath, 51 | [out_bestVersion] 52 | ); 53 | } 54 | 55 | /** 56 | * Gets the best matching version on the specified folder 57 | * Params: 58 | * folder = The folder containing the packageName versionentrie s 59 | * packageName = Used to build the path 60 | * subPackage = Currently used only for warning 61 | * packageVersion = The version required (SemVer) 62 | * Returns: 63 | */ 64 | private ReducedPackageInfo getPackageInFolder(string folder, string packageName, string subPackage, string packageVersion) 65 | { 66 | import std.path; 67 | import redub.misc.path; 68 | import std.file; 69 | import std.algorithm.sorting; 70 | import std.algorithm.iteration; 71 | import std.array; 72 | SemVer requirement = SemVer(packageVersion); 73 | if (requirement.isInvalid) 74 | { 75 | if (isGitStyle(requirement.toString)) 76 | { 77 | warn("Using git package version requirement ", requirement, " for ", packageName ~ (subPackage ? ( 78 | ":" ~ subPackage) : "")); 79 | foreach (DirEntry e; dirEntries(folder, SpanMode.shallow)) 80 | { 81 | string fileName = e.name.baseName; 82 | if (fileName == requirement.toString) 83 | return ReducedPackageInfo(fileName, redub.misc.path.buildNormalizedPath(folder, requirement.toString, packageName)); 84 | } 85 | } 86 | error("Invalid package version requirement ", requirement); 87 | } 88 | else 89 | { 90 | SemVer[] semVers = dirEntries(folder, SpanMode.shallow) 91 | .map!((DirEntry e) => e.name.baseName) 92 | .filter!((string name) => name.length && name[0] != '.') //Remove invisible files 93 | .map!((string name) => SemVer(name)) 94 | .array; 95 | foreach_reverse (SemVer v; sort(semVers)) ///Sorts version from the highest to lowest 96 | { 97 | if (v.satisfies(requirement)) 98 | return ReducedPackageInfo(v.toString, redub.misc.path.buildNormalizedPath(folder, v.toString, packageName), semVers); 99 | } 100 | } 101 | return ReducedPackageInfo.init; 102 | } 103 | 104 | /** 105 | * Lookups inside 106 | * - $HOME/.dub/packages/local-packages.json 107 | * - $HOME/.dub/packages/** 108 | * 109 | * Params: 110 | * packageName = "name" inside dub.json 111 | * packageVersion = "version" inside dub.json. SemVer matches are also accepted 112 | * Returns: The package path when found. null when not. 113 | */ 114 | PackageInfo getPackage(string packageName, string repo, string packageVersion, string requiredBy) 115 | { 116 | import std.file; 117 | import std.path; 118 | import redub.misc.path; 119 | import std.algorithm; 120 | import std.array; 121 | 122 | PackageInfo pack = basePackage(packageName, packageVersion , requiredBy); 123 | packageName = pack.packageName; 124 | vlog("Getting package ", packageName, ":", pack.subPackage, "@", packageVersion); 125 | ReducedPackageInfo localPackage = getPackageInLocalPackages(packageName, packageVersion); 126 | if (localPackage != ReducedPackageInfo.init) 127 | { 128 | pack.bestVersion = SemVer(localPackage.bestVersion); 129 | pack.path = localPackage.bestVersionPath; 130 | return pack; 131 | } 132 | 133 | ///If no version was downloaded yet, download before looking 134 | string downloadedPackagePath = redub.misc.path.buildNormalizedPath(getDefaultLookupPathForPackages(), packageName); 135 | ReducedPackageInfo info; 136 | if (!std.file.exists(downloadedPackagePath)) 137 | info = redubDownloadPackage(packageName, repo, packageVersion, requiredBy); 138 | else 139 | info = getPackageInFolder(downloadedPackagePath, packageName, pack.subPackage, packageVersion); 140 | 141 | if(info != ReducedPackageInfo.init) 142 | { 143 | pack.bestVersion = SemVer(info.bestVersion); 144 | pack.path = info.bestVersionPath; 145 | return pack; 146 | } 147 | 148 | ///If no matching version was found, try downloading it. 149 | info = redubDownloadPackage(packageName, repo, packageVersion, requiredBy); 150 | return getPackage(packageName, repo, packageVersion, requiredBy); 151 | 152 | throw new Exception( 153 | "Could not find any package named " ~ 154 | packageName ~ " with version " ~ packageVersion ~ 155 | " required by " ~ requiredBy ~ "\nFound versions:\n\t" ~ 156 | info.foundVersions.map!( 157 | (sv) => sv.toString).join("\n\t") 158 | ); 159 | } 160 | 161 | 162 | 163 | 164 | string getPackagePath(string packageName, string repo, string packageVersion, string requiredBy) 165 | { 166 | return getPackage(packageName, repo, packageVersion, requiredBy).path; 167 | } 168 | 169 | 170 | 171 | 172 | private ReducedPackageInfo getPackageInJSON(JSONValue json, string packageName, string packageVersion) 173 | { 174 | SemVer requirement = SemVer(packageVersion); 175 | foreach (v; json.array) 176 | { 177 | const(JSONValue)* nameJson = "name" in v; 178 | const(JSONValue)* ver = "version" in v; 179 | SemVer packageVer = SemVer(ver.str); 180 | if (nameJson && nameJson.str == packageName && packageVer.satisfies(requirement)) 181 | { 182 | vlog("Using local package found at ", v["path"].str, " with version ", ver.str); 183 | return ReducedPackageInfo(ver.str, v["path"].str); 184 | } 185 | } 186 | return ReducedPackageInfo.init; 187 | } 188 | 189 | /** 190 | * Use this version instead of getPackageInJSON since this one will cache the local packages instead. 191 | * Params: 192 | * packageName = The package name to get 193 | * packageVersion = The package version to get 194 | * Returns: Best version with its path 195 | */ 196 | private ReducedPackageInfo getPackageInLocalPackages(string packageName, string packageVersion) 197 | { 198 | import redub.misc.path; 199 | static import std.file; 200 | 201 | static JSONValue localCache; 202 | static bool isCached = false; 203 | 204 | if(isCached) 205 | { 206 | if(localCache == JSONValue.init) 207 | return ReducedPackageInfo.init; 208 | return getPackageInJSON(localCache, packageName, packageVersion); 209 | } 210 | isCached = true; 211 | string locPackages = buildNormalizedPath(getDefaultLookupPathForPackages(), "local-packages.json"); 212 | if(std.file.exists(locPackages)) 213 | localCache = parseJSON(std.file.readText(locPackages)); 214 | return getPackageInLocalPackages(packageName, packageVersion); 215 | } 216 | 217 | private string getDefaultLookupPathForPackages() 218 | { 219 | import redub.misc.path; 220 | return buildNormalizedPath(getDubWorkspacePath, "packages"); 221 | } 222 | 223 | /** 224 | * Git style (~master) 225 | * Params: 226 | * str = ~branchName 227 | * Returns: 228 | */ 229 | private bool isGitBranchStyle(string str) 230 | { 231 | import std.ascii : isAlphaNum; 232 | import std.algorithm.searching : canFind; 233 | 234 | // Must start with ~ and Can't find a non alpha numeric version 235 | return str.length > 1 && str[0] == '~' && 236 | !str[1 .. $].canFind!((ch) => !ch.isAlphaNum); 237 | } 238 | 239 | private bool isGitHashStyle(string str) 240 | { 241 | import std.ascii : isHexDigit; 242 | import std.algorithm.searching : canFind; 243 | 244 | // Can't find a non hex digit version 245 | return str.length > 0 && !str.canFind!((ch) => !ch.isHexDigit); 246 | } 247 | 248 | /** 249 | * Checks for both git branches and git hashes 250 | */ 251 | private bool isGitStyle(string str) 252 | { 253 | return isGitBranchStyle(str) || isGitHashStyle(str); 254 | } 255 | 256 | /** 257 | Dub's add-local outputs to 258 | $HOME/.dub/packages/local-packages.json 259 | [ 260 | { 261 | "name": "dorado", 262 | "path": "/home/artha/git/misc/dorado/", 263 | "version": "~main" 264 | }, 265 | { 266 | "name": "samerion-api", 267 | "path": "/home/artha/git/samerion/api/", 268 | "version": "~main" 269 | }, 270 | { 271 | "name": "steam-gns-d", 272 | "path": "/home/artha/git/samerion/steam-gns-d/", 273 | "version": "~main" 274 | }, 275 | { 276 | "name": "isodi", 277 | "path": "/home/artha/git/samerion/isodi/", 278 | "version": "~main" 279 | }, 280 | { 281 | "name": "rcdata", 282 | "path": "/home/artha/git/misc/rcdata/", 283 | "version": "~main" 284 | }, 285 | { 286 | "name": "smaugvm", 287 | "path": "/home/artha/git/samerion/smaug-vm/", 288 | "version": "~main" 289 | } 290 | ] 291 | **/ 292 | -------------------------------------------------------------------------------- /source/redub/package_searching/entry.d: -------------------------------------------------------------------------------- 1 | module redub.package_searching.entry; 2 | immutable string[] validEntryFiles = ["dub.json", "dub.sdl"]; 3 | 4 | /** 5 | * 6 | * Params: 7 | * workingDir = A non null working directory. Null is reserved for not found packages. Only absolute paths valid 8 | * Returns: An accepted project file type. 9 | */ 10 | string findEntryProjectFile(string workingDir, string recipe = null) 11 | { 12 | import std.path; 13 | static import std.file; 14 | if(recipe) 15 | return recipe; 16 | if(!workingDir.length) 17 | return null; 18 | 19 | foreach(entry; validEntryFiles) 20 | { 21 | string entryPath = getCachedNormalizedPath(workingDir, entry); 22 | if(std.file.exists(entryPath)) 23 | return entryPath; 24 | } 25 | return null; 26 | } 27 | 28 | private scope string getCachedNormalizedPath(string dir, string file) 29 | { 30 | static char[4096] entryCache; 31 | char[] temp = entryCache; 32 | import redub.misc.path; 33 | string ret = normalizePath(temp, dir, file); 34 | return ret; 35 | } -------------------------------------------------------------------------------- /source/redub/parsers/automatic.d: -------------------------------------------------------------------------------- 1 | module redub.parsers.automatic; 2 | import redub.logging; 3 | public import redub.buildapi; 4 | public import std.system; 5 | static import redub.parsers.json; 6 | static import redub.parsers.sdl; 7 | static import redub.parsers.environment; 8 | import redub.command_generators.commons; 9 | import redub.tree_generators.dub; 10 | 11 | /** 12 | * Parses an initial directory, not recursively. Currently only .sdl and .json are parsed. 13 | * After the parse happens, it also partially finish the requirements by using a generalized fix 14 | * for after the parsing stage. 15 | * Params: 16 | * projectWorkingDir = Optional working dir. What is the root being considered for the recipe file 17 | * cInfo = Important compilation info for that project 18 | * subConfiguration = Sub configuration to use 19 | * subPackage = Optional sub package 20 | * recipe = Optional recipe to read. It's path is not used as root. 21 | * parentName = Used whenever parseProject is called for a sub package. 22 | * isRoot = When the package is root, it is added to the package searching cache automatically with version 0.0.0 23 | * version = The actual version of that project, may be null on root 24 | * useExistingObj = Makes the project output dependencies if it is a root project. Disabled by default since compilation may be way slower 25 | * isDescribeOnly = Do not execute preGenerate commands when true 26 | * Returns: The build requirements to the project. Not recursive. 27 | */ 28 | BuildRequirements parseProject( 29 | string projectWorkingDir, 30 | CompilationInfo cInfo, 31 | BuildRequirements.Configuration subConfiguration, 32 | string subPackage, 33 | string recipe, 34 | string parentName = "", 35 | bool isRoot = false, 36 | string version_ = null, 37 | bool useExistingObj = false, 38 | bool isDescribeOnly = false 39 | ) 40 | { 41 | import std.path; 42 | import std.file; 43 | import redub.package_searching.entry; 44 | string projectFile = findEntryProjectFile(projectWorkingDir, recipe); 45 | if(!projectFile) 46 | throw new Exception("Directory '"~projectWorkingDir~"' has no recipe or does not exists."); 47 | BuildRequirements req; 48 | 49 | if(getLogLevel() >= LogLevel.verbose) 50 | { 51 | import std.string; 52 | if(projectFile.startsWith(projectWorkingDir)) 53 | vlog("Parsing ", projectFile); 54 | else 55 | vlog("Parsing ", projectFile, " at ", projectWorkingDir); 56 | if(subPackage) 57 | vlog("\tSubPackage: ", subPackage); 58 | if(subConfiguration.name) 59 | vlog("\tConfiguration: ", subConfiguration.name); 60 | } 61 | 62 | BuildConfiguration pending; 63 | switch(extension(projectFile)) 64 | { 65 | case ".sdl": req = redub.parsers.sdl.parse(projectFile, projectWorkingDir, cInfo, null, version_, subConfiguration, subPackage, pending, parentName, isDescribeOnly, isRoot); break; 66 | case ".json": req = redub.parsers.json.parse(projectFile, projectWorkingDir, cInfo, null, version_, subConfiguration, subPackage, pending, parentName, isDescribeOnly, isRoot); break; 67 | default: throw new Exception("Unsupported project type "~projectFile~" at dir "~projectWorkingDir); 68 | } 69 | return postProcessBuildRequirements(req, pending, cInfo, isRoot, useExistingObj); 70 | } 71 | 72 | /** 73 | * Post process for the parseProject operation. 74 | * Required for merge pending configuration, setup environment variables, parse environment variables inside its content 75 | * and define its current arch 76 | * Params: 77 | * req = Input requirement 78 | * cInfo = Compilation Info for setting the configuration arch 79 | * isRoot = Decides whether to output objects or not 80 | * useExistingObj = Decides whether to output objects or not 81 | * Returns: Post processed build requirements and now ready to use 82 | */ 83 | BuildRequirements postProcessBuildRequirements(BuildRequirements req, BuildConfiguration pending, CompilationInfo cInfo, bool isRoot, bool useExistingObj) 84 | { 85 | req.cfg.arch = cInfo.arch; 86 | req.extra.isRoot = isRoot; 87 | if(isRoot && useExistingObj) 88 | req.cfg.flags|= BuildConfigurationFlags.outputsDeps; 89 | 90 | partiallyFinishBuildRequirements(req, pending); ///Merge need to happen after partial finish, since other configuration will be merged 91 | req.cfg = redub.parsers.environment.parseEnvironment(req.cfg); 92 | return req; 93 | } 94 | 95 | /** 96 | * This function finishes some parts of the build requirement: 97 | * - Merge pending configuration (this guarantees the order is always correct.) 98 | * - Transforms relative paths into absolute paths 99 | * - Remove libraries from sourceFiles and put them onto libraries 100 | * - If no import directory exists, it will be reflected by source paths. 101 | * After that, it makes possible to merge with other build requirements. But, it is not completely 102 | * finished. It still has to become a tree. 103 | * Params: 104 | * req = Any build requirement 105 | */ 106 | private void partiallyFinishBuildRequirements(ref BuildRequirements req, BuildConfiguration pending) 107 | { 108 | import std.path; 109 | import std.algorithm.searching:startsWith; 110 | import redub.misc.path; 111 | req.cfg = req.cfg.merge(pending); 112 | if(!isAbsolute(req.cfg.outputDirectory)) 113 | req.cfg.outputDirectory = redub.misc.path.buildNormalizedPath(req.cfg.workingDir, req.cfg.outputDirectory); 114 | 115 | alias StringArrayRef = string[]*; 116 | scope StringArrayRef[] toAbsolutize = [ 117 | &req.cfg.importDirectories, 118 | &req.cfg.libraryPaths, 119 | &req.cfg.stringImportPaths, 120 | &req.cfg.sourcePaths, 121 | &req.cfg.excludeSourceFiles, 122 | &req.cfg.sourceFiles, 123 | &req.cfg.filesToCopy, 124 | ]; 125 | 126 | foreach(arr; toAbsolutize) 127 | { 128 | foreach(ref string dir; *arr) 129 | { 130 | import redub.command_generators.commons : escapePath; 131 | if(!isAbsolute(dir)) 132 | dir = redub.misc.path.buildNormalizedPath(req.cfg.workingDir, dir); 133 | } 134 | } 135 | 136 | for(int i = 1; i < req.cfg.sourcePaths.length; i++) 137 | { 138 | //[src, src/folder] 139 | //[src/folder, src] 140 | string pathRemoved; 141 | string specificPath; 142 | if(req.cfg.sourcePaths[i].length > req.cfg.sourcePaths[i-1].length && 143 | req.cfg.sourcePaths[i].startsWith(req.cfg.sourcePaths[i-1])) 144 | { 145 | specificPath = req.cfg.sourcePaths[i]; 146 | pathRemoved = req.cfg.sourcePaths[i-1]; 147 | req.cfg.sourcePaths[i-1] = req.cfg.sourcePaths[$-1]; 148 | } 149 | else if(req.cfg.sourcePaths[i-1].length > req.cfg.sourcePaths[i].length && 150 | req.cfg.sourcePaths[i-1].startsWith(req.cfg.sourcePaths[i])) 151 | { 152 | specificPath = req.cfg.sourcePaths[i-1]; 153 | pathRemoved = req.cfg.sourcePaths[i]; 154 | req.cfg.sourcePaths[i] = req.cfg.sourcePaths[$-1]; 155 | } 156 | if(pathRemoved !is null) 157 | { 158 | warn("Path ",pathRemoved," was removed from sourcePaths since a more specific path was specified [", specificPath,"] . Please use \"sourcePaths\": [] instead for removing that warn and be clear about your intention"); 159 | req.cfg.sourcePaths.length--; 160 | i--; 161 | } 162 | } 163 | 164 | 165 | import std.algorithm.iteration; 166 | auto libraries = req.cfg.sourceFiles.filter!((name) => name.extension.isLibraryExtension); 167 | req.cfg.libraries.exclusiveMergePaths(libraries); 168 | 169 | ///Remove libraries from the sourceFiles. 170 | req.cfg.sourceFiles = inPlaceFilter(req.cfg.sourceFiles, (string file) => !file.extension.isLibraryExtension); 171 | 172 | 173 | 174 | import std.algorithm.sorting; 175 | ///Sort dependencies for predictability 176 | sort!((Dependency a, Dependency b) 177 | { 178 | if(a.name != b.name) return a.name < b.name; 179 | return !a.subConfiguration.isDefault && b.subConfiguration.isDefault; 180 | })(req.dependencies); 181 | 182 | } 183 | 184 | 185 | 186 | void clearRecipeCaches() 187 | { 188 | redub.parsers.json.clearJsonRecipeCache(); 189 | redub.parsers.sdl.clearSdlRecipeCache(); 190 | } -------------------------------------------------------------------------------- /source/redub/parsers/base.d: -------------------------------------------------------------------------------- 1 | module redub.parsers.base; 2 | import std.system; 3 | import redub.command_generators.commons; 4 | import redub.logging; 5 | import redub.buildapi; 6 | import redub.package_searching.api; 7 | import redub.parsers.json; 8 | import redub.tree_generators.dub; 9 | 10 | 11 | struct ParseConfig 12 | { 13 | string workingDir; 14 | BuildRequirements.Configuration subConfiguration; 15 | ///Which subPackage to parse 16 | string subPackage; 17 | ///Which version this is 18 | string version_ = "~master"; 19 | ///Creates a filter for Compiler-Arch-OS-ISA 20 | CompilationInfo cInfo; 21 | ///The package name to use if recipe has no name 22 | string defaultPackageName; 23 | ParseSubConfig extra; 24 | ///When first run is equals false, it won't do anything at "configurations" 25 | bool firstRun = true; 26 | ///When preGenerateRun is true, it will run the preGenerateRun 27 | bool preGenerateRun = true; 28 | bool isRoot = false; 29 | } 30 | 31 | ///Supplemental information for ParseConfig 32 | struct ParseSubConfig 33 | { 34 | ///The requester of that package 35 | string requiredBy; 36 | ///If it has a parent, it is treated as a sub package, important for assembling the name. 37 | string parentName; 38 | 39 | bool isParsingSubpackage() const { return parentName.length != 0; } 40 | } 41 | 42 | 43 | void baseLoadPlugin(string pluginName, string pluginPath, string workingDir, CompilationInfo cInfo) 44 | { 45 | import redub.plugin.load; 46 | import std.file; 47 | import std.path; 48 | import redub.misc.path; 49 | if(!isAbsolute(pluginPath)) 50 | pluginPath = redub.misc.path.buildNormalizedPath(workingDir, pluginPath); 51 | loadPlugin(pluginName, pluginPath, cInfo); 52 | } 53 | void setName(ref BuildRequirements req, string name, ParseConfig c) 54 | { 55 | if(c.firstRun) 56 | { 57 | if(c.extra.parentName.length) 58 | name = c.extra.parentName~":"~name; 59 | req.cfg.name = name; 60 | if(!req.cfg.targetName.length) 61 | req.cfg.targetName = name; 62 | } 63 | } 64 | void setTargetName(ref BuildRequirements req, string name, ParseConfig c){req.cfg.targetName = name;} 65 | void setTargetPath(ref BuildRequirements req, string path, ParseConfig c){req.cfg.outputDirectory = path;} 66 | void setTargetType(ref BuildRequirements req, string targetType, ParseConfig c){req.cfg.targetType = targetFrom(targetType);} 67 | void addImportPaths(ref BuildRequirements req, JSONStringArray paths, ParseConfig c) 68 | { 69 | import std.array; 70 | if(c.firstRun) req.cfg.importDirectories = cast(string[])paths.array; 71 | else req.cfg.importDirectories.exclusiveMerge(paths); 72 | } 73 | void addStringImportPaths(ref BuildRequirements req, JSONStringArray paths, ParseConfig c){req.cfg.stringImportPaths.exclusiveMergePaths(paths);} 74 | void addExtraDependencyFiles(ref BuildRequirements req, JSONStringArray files, ParseConfig c){req.cfg.extraDependencyFiles.exclusiveMerge(files);} 75 | void addFilesToCopy(ref BuildRequirements req, JSONStringArray files, ParseConfig c){req.cfg.filesToCopy = req.cfg.filesToCopy.append(files);} 76 | void addPreGenerateCommands(ref BuildRequirements req, JSONStringArray cmds, ParseConfig c) 77 | { 78 | import hipjson; 79 | import redub.parsers.environment; 80 | if(c.preGenerateRun) 81 | { 82 | infos("Pre-gen ", "Running commands for ", c.extra.requiredBy); 83 | foreach(JSONValue cmd; cmds.save) 84 | { 85 | import std.process; 86 | import std.stdio; 87 | import std.conv:to; 88 | 89 | if(hasLogLevel(LogLevel.verbose)) 90 | vlog("Executing: ", executeShell("echo "~cmd.str, getRedubEnv, Config.none, size_t.max, c.workingDir).output, " at dir ", c.workingDir); 91 | 92 | auto status = wait(spawnShell(cmd.str, stdin, stdout, stderr, getRedubEnv, Config.none, c.workingDir)); 93 | if(status) 94 | throw new Exception("preGenerateCommand '"~cmd.str~"' exited with code "~status.to!string); 95 | } 96 | } 97 | if(req.cfg.commands.length < RedubCommands.preGenerate) 98 | req.cfg.commands.length = RedubCommands.preGenerate + 1; 99 | req.cfg.commands[RedubCommands.preGenerate] = req.cfg.commands[RedubCommands.preGenerate].append(cmds); 100 | } 101 | void addPostGenerateCommands(ref BuildRequirements req, JSONStringArray cmds, ParseConfig c) 102 | { 103 | if(req.cfg.commands.length < RedubCommands.postGenerate) 104 | req.cfg.commands.length = RedubCommands.postGenerate + 1; 105 | req.cfg.commands[RedubCommands.postGenerate] = req.cfg.commands[RedubCommands.postGenerate].append(cmds); 106 | } 107 | void addPreBuildPlugins(ref BuildRequirements req, string pluginName, JSONStringArray cmds, ParseConfig c) 108 | { 109 | string[] output; 110 | req.cfg.preBuildPlugins~= PluginExecution(pluginName, append(output, cmds)); 111 | } 112 | void addPreBuildCommands(ref BuildRequirements req, JSONStringArray cmds, ParseConfig c) 113 | { 114 | if(req.cfg.commands.length < RedubCommands.preBuild) 115 | req.cfg.commands.length = RedubCommands.preBuild + 1; 116 | req.cfg.commands[RedubCommands.preBuild] = req.cfg.commands[RedubCommands.preBuild].append(cmds); 117 | } 118 | void addPostBuildCommands(ref BuildRequirements req, JSONStringArray cmds, ParseConfig c) 119 | { 120 | if(req.cfg.commands.length < RedubCommands.postBuild) 121 | req.cfg.commands.length = RedubCommands.postBuild + 1; 122 | req.cfg.commands[RedubCommands.postBuild] = req.cfg.commands[RedubCommands.postBuild].append(cmds); 123 | } 124 | void addSourcePaths(ref BuildRequirements req, JSONStringArray paths, ParseConfig c) 125 | { 126 | import std.array; 127 | if(c.firstRun) req.cfg.sourcePaths = cast(string[])paths.array; 128 | else req.cfg.sourcePaths.exclusiveMergePaths(paths); 129 | } 130 | void addSourceFiles(ref BuildRequirements req, JSONStringArray files, ParseConfig c){req.cfg.sourceFiles.exclusiveMerge(files);} 131 | void addExcludedSourceFiles(ref BuildRequirements req, JSONStringArray files, ParseConfig c){req.cfg.excludeSourceFiles.exclusiveMerge(files);} 132 | void addLibPaths(ref BuildRequirements req, JSONStringArray paths, ParseConfig c){req.cfg.libraryPaths.exclusiveMerge(paths);} 133 | void addLibs(ref BuildRequirements req, JSONStringArray libs, ParseConfig c){req.cfg.libraries.exclusiveMerge(libs);} 134 | void addVersions(ref BuildRequirements req, JSONStringArray vers, ParseConfig c){req.cfg.versions.exclusiveMerge(vers);} 135 | void addDebugVersions(ref BuildRequirements req, JSONStringArray vers, ParseConfig c){req.cfg.debugVersions.exclusiveMerge(vers);} 136 | void addLinkFlags(ref BuildRequirements req, JSONStringArray lFlags, ParseConfig c){req.cfg.linkFlags.exclusiveMerge(lFlags);} 137 | void addDflags(ref BuildRequirements req, JSONStringArray dFlags, ParseConfig c){req.cfg.dFlags.exclusiveMerge(dFlags);} 138 | void addDependency( 139 | ref BuildRequirements req, 140 | ParseConfig c, 141 | string name, string version_, 142 | BuildRequirements.Configuration subConfiguration, 143 | string path, 144 | string visibility, 145 | PackageInfo* info, 146 | bool isOptional 147 | ) 148 | { 149 | import std.path; 150 | import redub.misc.path; 151 | import std.algorithm.searching:countUntil; 152 | if(path.length && !isAbsolute(path)) 153 | path = redub.misc.path.buildNormalizedPath(c.workingDir, path); 154 | Dependency dep = dependency(name, path, version_, req.name, c.workingDir, subConfiguration, visibility, info, isOptional); 155 | vvlog("Added dependency ", dep.name, ":", dep.subPackage, " [", dep.version_, "] ", "to ", req.name); 156 | //If dependency already exists, use the existing one 157 | ptrdiff_t depIndex = countUntil!((a) => a.isSameAs(dep))(req.dependencies); 158 | if(depIndex == -1) 159 | req.dependencies~= dep; 160 | else 161 | { 162 | dep.subConfiguration = req.dependencies[depIndex].subConfiguration; 163 | // if(!req.dependencies[depIndex].isOptional) dep.isOptional = false; 164 | req.dependencies[depIndex] = dep; 165 | } 166 | } 167 | 168 | 169 | /** 170 | * This function infers the subPackage name on the dependency. 171 | */ 172 | private Dependency dependency( 173 | string name, 174 | string path, 175 | string version_, 176 | string requirementName, 177 | string workingDir, 178 | BuildRequirements.Configuration subConfiguration, 179 | string visibilityStr, 180 | PackageInfo* info, 181 | bool isOptional 182 | ) 183 | { 184 | string out_mainPackageName; 185 | string subPackage = getSubPackageInfoRequiredBy(name, requirementName, out_mainPackageName); 186 | 187 | ///Inside this same package (requirementName:subPackage or :subPackage) 188 | if(subPackage && (out_mainPackageName == requirementName || out_mainPackageName.length == 0)) 189 | path = workingDir; 190 | 191 | Visibility visibility = Visibility.public_; 192 | if(visibilityStr) visibility = VisibilityFrom(visibilityStr); 193 | 194 | return Dependency(name, path, version_, subConfiguration, subPackage, visibility, info, isOptional); 195 | } 196 | 197 | void addSubConfiguration( 198 | ref BuildRequirements req, 199 | ParseConfig c, 200 | string dependencyName, 201 | string subConfigurationName 202 | ) 203 | { 204 | vlog("Using ", subConfigurationName, " subconfiguration for ", dependencyName, " in project ", c.extra.requiredBy); 205 | import std.algorithm.searching:countUntil; 206 | ptrdiff_t depIndex = countUntil!((dep) => dep.name == dependencyName)(req.dependencies); 207 | if(depIndex == -1) 208 | req.dependencies~= dependency(dependencyName, null, null, req.name, c.workingDir, BuildRequirements.Configuration(subConfigurationName, false), null, null, false); 209 | else 210 | req.dependencies[depIndex].subConfiguration = BuildRequirements.Configuration(subConfigurationName, false); 211 | } 212 | -------------------------------------------------------------------------------- /source/redub/parsers/build_type.d: -------------------------------------------------------------------------------- 1 | module redub.parsers.build_type; 2 | public import redub.buildapi; 3 | public import redub.compiler_identification; 4 | import redub.command_generators.d_compilers; 5 | 6 | 7 | 8 | __gshared BuildConfiguration[string] registeredBuildTypes; 9 | public void clearBuildTypesCache(){registeredBuildTypes = null; } 10 | 11 | 12 | BuildConfiguration parse(string buildType, AcceptedCompiler comp) 13 | { 14 | auto m = getFlagMapper(comp); 15 | BuildConfiguration* ret = buildType in registeredBuildTypes; 16 | if(ret) 17 | return *ret; 18 | BuildConfiguration b; 19 | 20 | 21 | switch(buildType) with(ValidDFlags) 22 | { 23 | case BuildType.debug_: b.dFlags = [m(debugMode), m(debugInfo)]; break; 24 | case BuildType.plain: break; 25 | case BuildType.release: b.dFlags = [m(releaseMode), m(optimize), m(inline)]; break; 26 | case BuildType.release_debug: b.dFlags = [m(releaseMode), m(optimize), m(inline), m(debugInfo)]; break; 27 | case BuildType.compiler_verbose: b.flags|= BuildConfigurationFlags.compilerVerbose; break; 28 | case BuildType.codegen_verbose: b.flags|= BuildConfigurationFlags.compilerVerboseCodeGen; break; 29 | case BuildType.time_trace: b.dFlags = [m(timeTrace), m(timeTraceFile)]; break; 30 | case BuildType.mixin_check: b.dFlags = [m(mixinFile)]; break; 31 | case BuildType.release_nobounds: b.dFlags = [m(releaseMode), m(optimize), m(inline), m(noBoundsCheck)]; break; 32 | case BuildType.unittest_: b.dFlags = [m(unittests), m(debugMode), m(debugInfo)]; break; 33 | case BuildType.profile: b.dFlags = [m(profile), m(debugInfo)]; break; 34 | case BuildType.profile_gc: b.dFlags = [m(profileGC), m(debugInfo)]; break; 35 | case BuildType.docs: throw new Exception("docs is not supported by redub"); 36 | case BuildType.ddox: throw new Exception("ddox is not supported by redub"); 37 | case BuildType.cov: b.dFlags = [m(coverage), m(debugInfo)]; break; 38 | case BuildType.cov_ctfe: b.dFlags = [m(coverageCTFE), m(debugInfo)]; break; 39 | case BuildType.unittest_cov: b.dFlags = [m(unittests), m(coverage), m(debugMode), m(debugInfo)]; break; 40 | case BuildType.unittest_cov_ctfe: b.dFlags = [m(unittests), m(coverageCTFE), m(debugMode), m(debugInfo)]; break; 41 | case BuildType.syntax: b.dFlags = [m(syntaxOnly)]; break; 42 | default: throw new Exception("Unknown build type "~buildType); 43 | } 44 | return b; 45 | } -------------------------------------------------------------------------------- /source/redub/parsers/sdl.d: -------------------------------------------------------------------------------- 1 | module redub.parsers.sdl; 2 | public import redub.buildapi; 3 | public import std.system; 4 | import hipjson; 5 | import redub.tree_generators.dub; 6 | 7 | /** 8 | * Converts SDL into a JSON file and parse it as a JSON 9 | * Uses filePath as fileData for parseWithData 10 | * Params: 11 | * filePath = The path in which the SDL file is located 12 | * workingDir = Working dir of the recipe 13 | * cInfo = Compilation Info filters 14 | * defaultPackageName = Default name, used for --single 15 | * version_ = Version being used 16 | * subConfiguration = The configuration to use 17 | * subPackage = The sub package to use 18 | * parentName = Used as metadata 19 | * isDescribeOnly = Used for not running the preGenerate commands 20 | * isRoot = metadata 21 | * Returns: The new build requirements 22 | */ 23 | BuildRequirements parse( 24 | string filePath, 25 | string workingDir, 26 | CompilationInfo cInfo, 27 | string defaultPackageName, 28 | string version_, 29 | BuildRequirements.Configuration subConfiguration, 30 | string subPackage, 31 | out BuildConfiguration pending, 32 | string parentName, 33 | bool isDescribeOnly = false, 34 | bool isRoot = false 35 | ) 36 | { 37 | import std.file; 38 | return parseWithData(filePath, 39 | readText(filePath), 40 | workingDir, 41 | cInfo, 42 | defaultPackageName, 43 | version_, 44 | subConfiguration, 45 | subPackage, 46 | pending, 47 | parentName, 48 | isDescribeOnly, 49 | isRoot, 50 | ); 51 | } 52 | 53 | /** 54 | * Converts SDL into a JSON file and parse it as a JSON 55 | * Params: 56 | * filePath = The path in which the SDL file is located 57 | * fileData = Uses that data instead of file path for parsing 58 | * workingDir = Working dir of the recipe 59 | * cInfo = Compilation Info filters 60 | * defaultPackageName = Default name, used for --single 61 | * version_ = Version being used 62 | * subConfiguration = The configuration to use 63 | * subPackage = The sub package to use 64 | * parentName = Metadata 65 | * isDescribeOnly = Used for not running preGenerate commands 66 | * isRoot = metadata 67 | * Returns: The new build requirements 68 | */ 69 | BuildRequirements parseWithData( 70 | string filePath, 71 | string fileData, 72 | string workingDir, 73 | CompilationInfo cInfo, 74 | string defaultPackageName, 75 | string version_, 76 | BuildRequirements.Configuration subConfiguration, 77 | string subPackage, 78 | out BuildConfiguration pending, 79 | string parentName, 80 | bool isDescribeOnly = false, 81 | bool isRoot = false 82 | ) 83 | { 84 | static import redub.parsers.json; 85 | import redub.parsers.base; 86 | 87 | ParseConfig c = ParseConfig(workingDir, subConfiguration, subPackage, version_, cInfo, defaultPackageName, ParseSubConfig(null, parentName), preGenerateRun: !isDescribeOnly); 88 | JSONValue json = parseSdlCached(filePath, fileData); 89 | BuildRequirements ret = redub.parsers.json.parse(json, c, pending, isRoot); 90 | return ret; 91 | } 92 | 93 | 94 | 95 | private JSONValue[string] sdlJsonCache; 96 | JSONValue parseSdlCached(string filePath, string fileData) 97 | { 98 | import dub_sdl_to_json; 99 | JSONValue* cached = filePath in sdlJsonCache; 100 | if(cached) return *cached; 101 | JSONValue ret = sdlToJSON(parseSDL(filePath, fileData)); 102 | if(ret.hasErrorOccurred) 103 | throw new Exception(ret.error); 104 | return sdlJsonCache[filePath] = ret; 105 | } 106 | 107 | void clearSdlRecipeCache(){sdlJsonCache = null;} -------------------------------------------------------------------------------- /source/redub/parsers/single.d: -------------------------------------------------------------------------------- 1 | module redub.parsers.single; 2 | import redub.parsers.automatic; 3 | import redub.logging; 4 | public import redub.buildapi; 5 | public import std.system; 6 | static import redub.parsers.json; 7 | static import redub.parsers.sdl; 8 | static import redub.parsers.environment; 9 | import redub.command_generators.commons; 10 | import redub.tree_generators.dub; 11 | 12 | /** 13 | * Parses an initial directory, not recursively. Currently only .sdl and .json are parsed. 14 | * After the parse happens, it also partially finish the requirements by using a generalized fix 15 | * for after the parsing stage. 16 | * Params: 17 | * projectWorkingDir = Optional working dir. What is the root being considered for the recipe file 18 | * compiler = Which compiler to use 19 | * subConfiguration = Sub configuration to use 20 | * subPackage = Optional sub package 21 | * recipe = Optional recipe to read. It's path is not used as root. 22 | * targetOS = Will be used to filter out some commands 23 | * isa = Instruction Set Architexture to use for filtering commands 24 | * isRoot = When the package is root, it is added to the package searching cache automatically with version 0.0.0 25 | * version = The actual version of that project, may be null on root 26 | * useExistingObj = Makes the project output dependencies if it is a root project. Disabled by default since compilation may be way slower 27 | * Returns: The build requirements to the project. Not recursive. 28 | */ 29 | BuildRequirements parseProject( 30 | string projectWorkingDir, 31 | CompilationInfo cInfo, 32 | BuildRequirements.Configuration subConfiguration, 33 | string subPackage, 34 | string recipe, 35 | bool isRoot = false, 36 | string version_ = null, 37 | bool useExistingObj = false 38 | ) 39 | { 40 | import std.path; 41 | import std.file; 42 | import redub.package_searching.entry; 43 | if(!std.file.exists(projectWorkingDir)) 44 | throw new Exception("Directory '"~projectWorkingDir~"' does not exists."); 45 | 46 | SingleFileData singleInfo = readConfigurationFromFile(recipe); 47 | 48 | inLogLevel(LogLevel.vverbose, infos("Single Recipe", "'", singleInfo.defaultPackageName, "': ", singleInfo.recipe)); 49 | BuildRequirements req; 50 | BuildConfiguration pending; 51 | switch(extension(singleInfo.fileName)) 52 | { 53 | case ".sdl": req = redub.parsers.sdl.parseWithData(recipe, singleInfo.recipe, projectWorkingDir, cInfo, singleInfo.defaultPackageName, version_, subConfiguration, subPackage, pending, "", false, isRoot); break; 54 | case ".json": req = redub.parsers.json.parseWithData(recipe, singleInfo.recipe, projectWorkingDir, cInfo, singleInfo.defaultPackageName ,version_, subConfiguration, subPackage, pending, "", false, isRoot); break; 55 | default: throw new Exception("Unsupported project type "~recipe~" at dir "~projectWorkingDir); 56 | } 57 | req.cfg.targetType = TargetType.executable; 58 | req.cfg.outputDirectory = dirName(recipe); 59 | req.cfg.sourceFiles.exclusiveMerge([recipe]); 60 | 61 | return postProcessBuildRequirements(req, pending, cInfo, isRoot, useExistingObj); 62 | } 63 | 64 | 65 | 66 | struct SingleFileData 67 | { 68 | string recipe; 69 | string fileName; 70 | string defaultPackageName; 71 | } 72 | 73 | 74 | /** 75 | * Most of it took from https://github.com/dlang/dub/blob/059291d1e4438a7925be4923e8b93495643d205e/source/dub/dub.d#L559 76 | * Params: 77 | * file = The file which contains a dub recipe to be parsed, must start with a comment using /+ +/, and contain the type of it: 78 | * e.g: 79 | ```d 80 | /+ 81 | dub.json: 82 | { 83 | "dependencies": { 84 | "redub": "~>1.14.16" 85 | } 86 | } 87 | +/ 88 | ``` 89 | * Returns: 90 | */ 91 | SingleFileData readConfigurationFromFile(string file) 92 | { 93 | import std.file; 94 | import std.string; 95 | import std.exception; 96 | import std.path; 97 | string data = readText(file); 98 | 99 | if (data.startsWith("#!")) { 100 | auto idx = data.indexOf('\n'); 101 | enforce(idx > 0, "The source fine doesn't contain anything but a shebang line."); 102 | data = data[idx+1 .. $]; 103 | } 104 | 105 | string recipeContent; 106 | 107 | if (data.startsWith("/+")) { 108 | data = data[2 .. $]; 109 | auto idx = data.indexOf("+/"); 110 | enforce(idx >= 0, "Missing \"+/\" to close comment."); 111 | recipeContent = data[0 .. idx].strip(); 112 | } 113 | else 114 | throw new Exception("The source file must start with a recipe comment."); 115 | 116 | auto nidx = recipeContent.indexOf('\n'); 117 | auto idx = recipeContent.indexOf(':'); 118 | 119 | enforce(idx > 0 && (nidx < 0 || nidx > idx), 120 | "The first line of the recipe comment must list the recipe file name followed by a colon (e.g. \"/+ dub.sdl:\")."); 121 | string fileName = recipeContent[0 .. idx]; 122 | recipeContent = recipeContent[idx+1 .. $]; 123 | string defaultPackageName = file.baseName.stripExtension.strip; 124 | 125 | return SingleFileData( 126 | recipeContent, 127 | fileName, 128 | defaultPackageName 129 | ); 130 | } -------------------------------------------------------------------------------- /source/redub/plugin/api.d: -------------------------------------------------------------------------------- 1 | module redub.plugin.api; 2 | 3 | enum RedubPluginExitCode : ubyte 4 | { 5 | ///Indicates that the build may continue 6 | success = 0, 7 | ///The build should not continue 8 | error = 1, 9 | } 10 | extern(C) struct RedubPluginStatus 11 | { 12 | RedubPluginExitCode code; 13 | ///If there is some message that Redub should print, just assign it here. 14 | string message; 15 | 16 | static RedubPluginStatus success() { return RedubPluginStatus(RedubPluginExitCode.success);} 17 | } 18 | 19 | 20 | /** 21 | * Due to bugs regarding DMD and LDC bridging on shared libraries, functions that returns a value must use a return ref argument, this ensures 22 | * compatibility between both compilers 23 | */ 24 | interface RedubPlugin 25 | { 26 | void preGenerate(); 27 | void postGenerate(); 28 | 29 | /** 30 | * 31 | * Params: 32 | * input = Receives the build information on the input 33 | * out = The modified input. If RedubPluginData is the same as the .init value, it will be completely ignored in the modification process 34 | * args = Arguments that may be sent or not from the redub configuration file. 35 | * status = The return value 36 | * Returns: The status for managing redub state. 37 | */ 38 | extern(C) ref RedubPluginStatus preBuild(RedubPluginData input, out RedubPluginData output, const ref string[] args, return ref RedubPluginStatus status); 39 | void postBuild(); 40 | } 41 | 42 | /** 43 | * Representation of what may be changed by a redub plugin. 44 | * All member names are kept the same as redub internal representation. 45 | */ 46 | struct RedubPluginData 47 | { 48 | string[] versions; 49 | string[] debugVersions; 50 | string[] importDirectories; 51 | string[] libraryPaths; 52 | string[] stringImportPaths; 53 | string[] libraries; 54 | string[] linkFlags; 55 | string[] dFlags; 56 | string[] sourcePaths; 57 | string[] sourceFiles; 58 | string[] excludeSourceFiles; 59 | string[] extraDependencyFiles; 60 | string[] filesToCopy; 61 | ///Do not rely on that, since it needs useExistingObjFiles on 62 | string[] changedBuildFiles; 63 | string outputDirectory; 64 | string targetName; 65 | } 66 | 67 | version(RedubPlugin): 68 | mixin template PluginEntrypoint(cls) if(is(cls : RedubPlugin)) 69 | { 70 | version(Windows) 71 | { 72 | import core.sys.windows.dll; 73 | mixin SimpleDllMain; 74 | } 75 | 76 | pragma(mangle, "plugin_"~__MODULE__) 77 | export extern(C) RedubPlugin pluginEntrypoint() 78 | { 79 | return new cls(); 80 | } 81 | } -------------------------------------------------------------------------------- /source/redub/plugin/build.d: -------------------------------------------------------------------------------- 1 | /** 2 | * This module provides a way to build redub plugins. 3 | * Redub plugins are meant to be a replacement for usage of rdmd 4 | * Since rdmd is an external program, it's cache evaluation is not so good, and it doesn't have 5 | * a real integration with build system, a build plugin is the best way for improving it 6 | */ 7 | module redub.plugin.build; 8 | import redub.buildapi; 9 | import core.simd; 10 | import redub.tree_generators.dub; 11 | 12 | version(DigitalMars) 13 | enum DefaultCompiler = "dmd"; 14 | else 15 | enum DefaultCompiler = "ldc2"; 16 | 17 | 18 | private immutable string apiImport = import("redub/plugin/api.d"); 19 | 20 | string getPluginImportDir() 21 | { 22 | import redub.building.cache; 23 | import redub.misc.path; 24 | return buildNormalizedPath(getCacheFolder(), "plugins", "import"); 25 | } 26 | private void writePluginImport() 27 | { 28 | import redub.misc.path; 29 | import std.file; 30 | string importPath = buildNormalizedPath(getPluginImportDir(), "redub", "plugin"); 31 | if(!exists(importPath)) 32 | mkdirRecurse(importPath); 33 | 34 | string importFilePath = buildNormalizedPath(importPath, "api.d"); 35 | 36 | if(exists(importFilePath)) 37 | { 38 | static string cachedFile; 39 | if(cachedFile == null) 40 | cachedFile = readText(importFilePath); 41 | if(cachedFile == apiImport) 42 | return; 43 | } 44 | 45 | std.file.write(buildNormalizedPath(importPath, "api.d"), apiImport); 46 | } 47 | 48 | BuildConfiguration injectPluginCfg(BuildConfiguration base, string pluginName, CompilationInfo cInfo) 49 | { 50 | import redub.building.cache; 51 | base.targetName = base.name = pluginName; 52 | base.versions~= "RedubPlugin"; 53 | base.dFlags~= "-i"; 54 | base.sourcePaths~= getPluginImportDir(); 55 | base.importDirectories~= getPluginImportDir(); 56 | base.targetType = TargetType.dynamicLibrary; 57 | 58 | return base; 59 | } 60 | 61 | void buildPlugin(string pluginName, string inputFile, CompilationInfo cInfo) 62 | { 63 | import redub.command_generators.automatic; 64 | import redub.command_generators.commons; 65 | import redub.building.utils; 66 | import redub.building.cache; 67 | import redub.compiler_identification; 68 | import redub.logging; 69 | import std.path; 70 | import std.file; 71 | writePluginImport(); 72 | 73 | 74 | if(exists(inputFile) && isDir(inputFile)) 75 | { 76 | buildPluginProject(inputFile, cInfo); 77 | return; 78 | } 79 | 80 | 81 | 82 | BuildConfiguration b = injectPluginCfg(BuildConfiguration.init, pluginName, cInfo); 83 | b.sourceFiles = [inputFile]; 84 | 85 | 86 | CompilingSession s = CompilingSession(getCompiler(DefaultCompiler), os, instructionSetArchitecture); 87 | 88 | string buildCmds; 89 | string pluginHash = hashFrom(b, s, true); 90 | string inDir = getCacheOutputDir(pluginHash, b, s, true); 91 | 92 | errorTitle(pluginHash, " ", hashFrom(b, s, false)); 93 | 94 | errorTitle("", execCompiler(b, s.compiler.d.bin, getCompilationFlags(b, s, pluginHash, true), buildCmds, s.compiler, inDir).output); 95 | errorTitle("Plugin Flags: ", buildCmds); 96 | errorTitle("", linkBase(const ThreadBuildData(b, ExtraInformation()), s, pluginHash, buildCmds).output); 97 | errorTitle("Plugin Flags: ", buildCmds); 98 | } 99 | 100 | 101 | void buildPluginProject(string pluginDir, CompilationInfo cInfo) 102 | { 103 | import redub.api; 104 | import redub.logging; 105 | writePluginImport(); 106 | 107 | LogLevel level = getLogLevel(); 108 | setLogLevel(LogLevel.error); 109 | 110 | version(LDC) 111 | enum preferredCompiler = "ldc2"; 112 | else 113 | enum preferredCompiler = "dmd"; 114 | 115 | ProjectDetails pluginDetails = resolveDependencies(false, os, CompilationDetails(preferredCompiler, includeEnvironmentVariables: false), ProjectToParse(null, pluginDir)); 116 | if(!pluginDetails.tree) 117 | throw new Exception("Could not build plugin at path "~pluginDir); 118 | 119 | pluginDetails.bCreateSelections = false; 120 | pluginDetails.tree.requirements.cfg = injectPluginCfg(pluginDetails.tree.requirements.cfg, pluginDetails.tree.name, cInfo); 121 | pluginDetails = buildProject(pluginDetails); 122 | setLogLevel(level); 123 | } -------------------------------------------------------------------------------- /source/redub/plugin/load.d: -------------------------------------------------------------------------------- 1 | module redub.plugin.load; 2 | import redub.plugin.api; 3 | import redub.buildapi; 4 | import redub.tree_generators.dub; 5 | 6 | 7 | private struct RegisteredPlugin 8 | { 9 | RedubPlugin plugin; 10 | string path; 11 | } 12 | 13 | __gshared RegisteredPlugin[string] registeredPlugins; 14 | void loadPlugin(string pluginName, string pluginPath, CompilationInfo cInfo) 15 | { 16 | import redub.plugin.build; 17 | import std.file; 18 | import redub.misc.path; 19 | 20 | RegisteredPlugin* reg; 21 | synchronized 22 | { 23 | reg = pluginName in registeredPlugins; 24 | if(reg) 25 | { 26 | if(reg.path != pluginPath) 27 | throw new Exception("Attempt to register plugin with same name '"~pluginName~"' with different paths: '"~reg.path~"' vs '"~pluginPath~"'"); 28 | return; 29 | } 30 | } 31 | 32 | 33 | 34 | buildPlugin(pluginName, pluginPath, cInfo); 35 | 36 | string fullPluginPath = buildNormalizedPath(pluginPath, pluginName); 37 | 38 | void* pluginDll = loadLib((getDynamicLibraryName(fullPluginPath)~"\0").ptr); 39 | if(!pluginDll) 40 | throw new Exception("Plugin "~pluginName~" could not be loaded. Tried with path '"~fullPluginPath~"'. Error '"~sysError~"'"); 41 | void* pluginFunc = loadSymbol(pluginDll, ("plugin_"~pluginName~"\0").ptr); 42 | if(!pluginFunc) 43 | throw new Exception("Plugin function 'plugin_"~pluginName~"' not found. Maybe you forgot to `import redub.plugin.api; mixin PluginEntrypoint!(YourPluginClassName)?`"); 44 | 45 | import redub.logging; 46 | 47 | infos("Plugin Loaded: ", pluginName, " [", pluginPath, "]"); 48 | 49 | synchronized 50 | { 51 | registeredPlugins[pluginName] = RegisteredPlugin((cast(RedubPlugin function())pluginFunc)(), pluginPath); 52 | } 53 | } 54 | 55 | BuildConfiguration executePlugin(string pluginName, BuildConfiguration cfg, string[] args) 56 | { 57 | import redub.logging; 58 | RegisteredPlugin* reg; 59 | synchronized 60 | { 61 | reg = pluginName in registeredPlugins; 62 | if(!reg) 63 | { 64 | import std.conv:to; 65 | throw new Exception("Could not find registered plugin named '"~pluginName~"'. Registered Plugins: "~registeredPlugins.to!string); 66 | } 67 | } 68 | 69 | RedubPlugin plugin = reg.plugin; 70 | RedubPluginData preBuildResult; 71 | RedubPluginStatus status; 72 | try 73 | { 74 | status = plugin.preBuild(cfg.toRedubPluginData, preBuildResult, args, status); 75 | if(status.message) 76 | { 77 | if(status.code == RedubPluginExitCode.success) 78 | infos("Plugin '"~pluginName~"': ", status.message); 79 | else 80 | errorTitle("Plugin '"~pluginName~"': ", status.message); 81 | } 82 | } 83 | catch (Exception e) 84 | { 85 | errorTitle("Plugin '"~pluginName~"' failed with an exception: ", e.msg); 86 | } 87 | if(status.code != RedubPluginExitCode.success) 88 | throw new Exception("Execute Plugin process Failed for "~cfg.name); 89 | if(preBuildResult != RedubPluginData.init) 90 | return cfg.mergeRedubPlugin(preBuildResult); 91 | 92 | 93 | return cfg; 94 | 95 | } 96 | 97 | private { 98 | version(Windows) 99 | { 100 | import core.sys.windows.winbase; 101 | import core.sys.windows.windef; 102 | HMODULE loadLib(const(char)* name){ 103 | return LoadLibraryA(name); 104 | } 105 | 106 | void unloadLib(void* lib){ 107 | FreeLibrary(lib); 108 | } 109 | 110 | void* loadSymbol(void* lib, const(char)* symbolName){ 111 | return GetProcAddress(lib, symbolName); 112 | } 113 | string sysError(){ 114 | import std.conv:to; 115 | wchar* msgBuf; 116 | enum uint langID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 117 | 118 | FormatMessageW( 119 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 120 | FORMAT_MESSAGE_FROM_SYSTEM | 121 | FORMAT_MESSAGE_IGNORE_INSERTS, 122 | null, 123 | GetLastError(), 124 | langID, 125 | cast(wchar*)&msgBuf, 126 | 0, 127 | null, 128 | ); 129 | string ret; 130 | if(msgBuf) 131 | { 132 | ret = to!string(msgBuf); 133 | LocalFree(msgBuf); 134 | } 135 | else 136 | return "Unknown Error"; 137 | return ret; 138 | } 139 | } 140 | else version(Posix) 141 | { 142 | import core.sys.posix.dlfcn; 143 | 144 | void* loadLib(const(char)* name) 145 | { 146 | return dlopen(name, RTLD_NOW); 147 | } 148 | 149 | void unloadLib(void* lib){ 150 | dlclose(lib); 151 | } 152 | 153 | void* loadSymbol(void* lib, const(char)* symbolName){ 154 | return dlsym(lib, symbolName); 155 | } 156 | 157 | string sysError(){ 158 | import core.stdc.string; 159 | char* msg = dlerror(); 160 | return cast(string)(msg ? msg[0..strlen(msg)] : "Unknown Error"); 161 | } 162 | 163 | } 164 | else static assert(false, "No dll loading support for this platform"); 165 | } 166 | 167 | string getDynamicLibraryName(string dynamicLibPath) 168 | { 169 | import std.path; 170 | string dir = dirName(dynamicLibPath); 171 | string name = baseName(dynamicLibPath); 172 | version(Windows) 173 | return buildNormalizedPath(dir, name~".dll"); 174 | else version(linux) 175 | return buildNormalizedPath(dir, "lib"~name~".so"); 176 | else version(OSX) 177 | return buildNormalizedPath(dir, "lib"~name~".dylib"); 178 | else static assert(false, "No support for dynamic libraries on that OS."); 179 | } -------------------------------------------------------------------------------- /source/redub/tree_generators/dub.d: -------------------------------------------------------------------------------- 1 | module redub.tree_generators.dub; 2 | import redub.logging; 3 | import redub.buildapi; 4 | import redub.package_searching.dub; 5 | import redub.package_searching.entry; 6 | import redub.parsers.automatic; 7 | 8 | 9 | struct CompilationInfo 10 | { 11 | string compiler; 12 | string c_compiler; 13 | string arch; 14 | OS targetOS; 15 | ///Target Instruction Set Architecture 16 | ISA isa; 17 | ///Where the actual compiler is. Used for plugin building 18 | string binPath; 19 | } 20 | 21 | 22 | /** 23 | * This function receives an already parsed project path (BuildRequirements) and finishes parsing 24 | * its dependees. While it parses them, it also merge the root build flags with their dependees and 25 | * does that recursively. 26 | * 27 | * If a project with the same name is found, it is merged with its existing counterpart 28 | * 29 | * It also clears the jsonCache so it is better suited for a library 30 | * 31 | * Params: 32 | * req = Root project to build 33 | * Returns: A tree out of the BuildRequirements, with all its compilation flags merged. It is the final step 34 | * before being able to correctly use the compilation flags 35 | */ 36 | ProjectNode getProjectTree(BuildRequirements req, CompilationInfo info) 37 | { 38 | import redub.parsers.json; 39 | import std.datetime.stopwatch; 40 | ProjectNode tree = new ProjectNode(req, false); 41 | string[string] subConfigs = req.getSubConfigurations; 42 | ProjectNode[string] visited; 43 | ProjectNode[] queue = [tree]; 44 | getProjectTreeImpl(queue, info, subConfigs, visited); 45 | detectCycle(tree); 46 | 47 | StopWatch sw = StopWatch(AutoStart.no); 48 | if(hasLogLevel(LogLevel.vverbose)) 49 | sw.start(); 50 | tree.finish(info.targetOS, info.isa); 51 | if(hasLogLevel(LogLevel.vverbose)) 52 | infos("Tree Merged: '", tree.name, "' merged in ", sw.peek.total!"msecs", "ms"); 53 | clearRecipeCaches(); 54 | return tree; 55 | } 56 | 57 | 58 | void detectCycle(ProjectNode t) 59 | { 60 | bool[ProjectNode] visited; 61 | bool[ProjectNode] inStack; 62 | 63 | void impl(ProjectNode node) 64 | { 65 | if(node in inStack) throw new Exception("Found a cycle at "~node.name); 66 | inStack[node] = true; 67 | visited[node] = true; 68 | 69 | foreach(n; node.dependencies) 70 | { 71 | if(!(n in visited)) impl(n); 72 | } 73 | inStack.remove(node); 74 | } 75 | impl(t); 76 | } 77 | 78 | /** 79 | * 80 | * Params: 81 | * queue = A queue for breadth first traversal 82 | * info = Compiler information for parsing nodes 83 | * subConfigurations = A map of subConfiguration[dependencyName] for mapping sub configuration matching 84 | * visited = Cache for unique matching 85 | * for saving CPU and memory instead if needing to recursively iterate all the time. 86 | * this was moved here because it already implements the `visited` pattern inside the tree, 87 | * so, it is an assumption that can be made to make it slightly faster. Might be removed 88 | * if it makes code comprehension significantly worse. 89 | * 90 | * Returns: 91 | */ 92 | private void getProjectTreeImpl( 93 | ref ProjectNode[] queue, 94 | CompilationInfo info, 95 | string[string] subConfigurations, 96 | ref ProjectNode[string] visited, 97 | ) 98 | { 99 | if(queue.length == 0) return; 100 | ProjectNode node = queue[0]; 101 | foreach(dep; node.requirements.dependencies) 102 | { 103 | if(dep.isSubConfigurationOnly) 104 | continue; 105 | ProjectNode* visitedDep = dep.name in visited; 106 | ProjectNode depNode; 107 | if(dep.subConfiguration.isDefault && dep.name in subConfigurations) 108 | dep.subConfiguration = BuildRequirements.Configuration(subConfigurations[dep.name], false); 109 | if(visitedDep) 110 | { 111 | depNode = *visitedDep; 112 | 113 | if(!dep.isOptional && depNode.isOptional) 114 | { 115 | depNode.makeRequired(); 116 | infos("Optional Included: ", dep.name); 117 | } 118 | ///When found 2 different packages requiring a different dependency subConfiguration 119 | /// and the new is a default one. 120 | if(visitedDep.requirements.configuration != dep.subConfiguration && !dep.subConfiguration.isDefault) 121 | { 122 | BuildRequirements depConfig = parseDependency(dep, node.requirements, info); 123 | if(visitedDep.requirements.targetConfiguration != depConfig.targetConfiguration) 124 | { 125 | //Print merging different subConfigs? 126 | visitedDep.requirements = mergeDifferentSubConfigurations( 127 | visitedDep.requirements, 128 | depConfig 129 | ); 130 | } 131 | } 132 | else if(visitedDep.requirements.version_ != dep.version_) 133 | { 134 | // error("Found different versions to parse: ", visitedDep.name, " ", visitedDep.requirements.version_, " vs ", dep.version_); 135 | BuildRequirements depConfig = parseDependency(dep, node.requirements, info); 136 | visitedDep.requirements = depConfig; 137 | } 138 | 139 | } 140 | else 141 | { 142 | depNode = new ProjectNode(parseDependency(dep, node.requirements, info), dep.isOptional); 143 | if(dep.name != depNode.name) 144 | { 145 | import redub.api; 146 | throw new RedubException("Dependency '"~dep.name~"' specified at path '"~dep.path~"' matches '"~depNode.name~"'. The dependency name should correctly match the one found at that path."); 147 | } 148 | ///TODO: Improve dependency cycle detection 149 | if(dep.name == node.name) 150 | { 151 | import redub.api; 152 | throw new RedubException("Package '"~dep.name~"' at path '"~dep.path~"' can't depend on itself."); 153 | } 154 | subConfigurations = depNode.requirements.mergeSubConfigurations(subConfigurations); 155 | visited[dep.name] = depNode; 156 | queue~= depNode; 157 | } 158 | node.addDependency(depNode); 159 | } 160 | 161 | queue = queue[1..$]; 162 | getProjectTreeImpl(queue, info, subConfigurations, visited); 163 | } 164 | 165 | 166 | private BuildRequirements parseDependency(Dependency dep, BuildRequirements parent, CompilationInfo info) 167 | { 168 | import redub.package_searching.cache; 169 | import redub.logging; 170 | import std.datetime.stopwatch; 171 | 172 | StopWatch sw = StopWatch(AutoStart.no); 173 | 174 | if(getLogLevel() >= LogLevel.vverbose) 175 | { 176 | sw.start(); 177 | vvlog("Parsing ",parent.name, "->", dep.name, " ", dep.version_); 178 | } 179 | BuildRequirements depReq = parseProject(dep.pkgInfo.path, info, dep.subConfiguration, dep.subPackage, null); 180 | if(getLogLevel() >= LogLevel.vverbose) 181 | { 182 | infos(dep.name, " parsed in ", sw.peek.total!"msecs", "ms"); 183 | } 184 | return depReq; 185 | } 186 | 187 | 188 | private BuildRequirements mergeDifferentSubConfigurations(BuildRequirements existingReq, BuildRequirements newReq) 189 | { 190 | if(existingReq.configuration.isDefault) 191 | return newReq; 192 | throw new Exception( 193 | "Error in project: '"~existingReq.name~"' Can't merge different subConfigurations at this " ~ 194 | "moment: "~existingReq.targetConfiguration~ " vs " ~ newReq.targetConfiguration 195 | ); 196 | } 197 | 198 | void printProjectTree(ProjectNode root) 199 | { 200 | bool[ProjectNode] visit; 201 | void printProjectTreeImpl(ProjectNode node, int depth, ref bool[ProjectNode] visited) 202 | { 203 | info("\t".repeat(depth), node.name, " [", node.requirements.configuration.name, "] ", node.requirements.version_); 204 | if(!(node in visited)) 205 | { 206 | foreach(dep; node.dependencies) 207 | printProjectTreeImpl(dep, depth+1, visited); 208 | } 209 | visited[node] = true; 210 | } 211 | 212 | printProjectTreeImpl(root, 0, visit); 213 | } 214 | 215 | string repeat(string v, int n) 216 | { 217 | if(n <= 0) return null; 218 | char[] ret = new char[](v.length*n); 219 | foreach(i; 0..n) 220 | ret[i*v.length..(i+1)*v.length] = v[]; 221 | return cast(string)ret; 222 | } -------------------------------------------------------------------------------- /tests/deps/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /deps 6 | deps.so 7 | deps.dylib 8 | deps.dll 9 | deps.a 10 | deps.lib 11 | deps-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/deps/a/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /a 6 | a.so 7 | a.dylib 8 | a.dll 9 | a.a 10 | a.lib 11 | a-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/deps/a/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "targetType": "library", 6 | "versions": [ 7 | "AVer" 8 | ], 9 | "dependencies": { 10 | "x": {"path": "../x"} 11 | }, 12 | "subConfigurations": { 13 | "x": "special" 14 | }, 15 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 16 | "description": "A minimal D application.", 17 | "license": "proprietary", 18 | "name": "a" 19 | } 20 | -------------------------------------------------------------------------------- /tests/deps/a/source/a.d: -------------------------------------------------------------------------------- 1 | module a; 2 | 3 | string strA; 4 | string b; -------------------------------------------------------------------------------- /tests/deps/b/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /b 6 | b.so 7 | b.dylib 8 | b.dll 9 | b.a 10 | b.lib 11 | b-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/deps/b/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "targetType": "staticLibrary", 6 | "versions": [ 7 | "BVer" 8 | ], 9 | "dependencies": { 10 | "x": {"path": "../x"} 11 | }, 12 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 13 | "description": "A minimal D application.", 14 | "license": "proprietary", 15 | "name": "b" 16 | } -------------------------------------------------------------------------------- /tests/deps/b/source/b.d: -------------------------------------------------------------------------------- 1 | module b; 2 | 3 | string strB; 4 | version(Have_a) 5 | { 6 | pragma(msg, "B has A??"); 7 | } -------------------------------------------------------------------------------- /tests/deps/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "dependencies": { 6 | "a": {"path": "a"}, 7 | "b": {"path": "b"}, 8 | "x": {"path": "x"} 9 | }, 10 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 11 | "description": "A minimal D application.", 12 | "license": "proprietary", 13 | "name": "deps" 14 | } -------------------------------------------------------------------------------- /tests/deps/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import common; 3 | 4 | void main() 5 | { 6 | commonStr = "Hello"; 7 | writeln("Edit source/app.d to start your project. Abc"); 8 | } 9 | -------------------------------------------------------------------------------- /tests/deps/x/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /x 6 | x.so 7 | x.dylib 8 | x.dll 9 | x.a 10 | x.lib 11 | x-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/deps/x/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | 6 | "configurations": [ 7 | { 8 | "name": "special", 9 | "versions": [ 10 | "DoesDubSendsThisVersionToDeps" 11 | ] 12 | }, 13 | { 14 | "name": "other" 15 | } 16 | ], 17 | "targetType": "library", 18 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 19 | "description": "A minimal D application.", 20 | "license": "proprietary", 21 | "name": "x" 22 | } 23 | -------------------------------------------------------------------------------- /tests/deps/x/source/common.d: -------------------------------------------------------------------------------- 1 | module common; 2 | 3 | string commonStr; -------------------------------------------------------------------------------- /tests/gcc/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Mancini", "Amlal El Mahrouss" 4 | ], 5 | "language": "C", 6 | "targetType": "executable", 7 | "copyright": "Copyright © 2024, Amlal El Mahrouss", 8 | "description": "A minimal C++ application.", 9 | "license": "MIT", 10 | "name": "gcc_example" 11 | } -------------------------------------------------------------------------------- /tests/gcc/source/gcc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char const *argv[]) 4 | { 5 | printf("Hi\n"); 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/gxx/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Mancini", "Amlal El Mahrouss" 4 | ], 5 | "language": "C", 6 | "dflags": [ "--std=c++20" ], 7 | "targetType": "executable", 8 | "copyright": "Copyright © 2024, Amlal El Mahrouss", 9 | "description": "A minimal C++ application.", 10 | "license": "MIT", 11 | "name": "gxx_example" 12 | } -------------------------------------------------------------------------------- /tests/gxx/source/gcc.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char const *argv[]) 5 | { 6 | if constexpr (std::endian::native == std::endian::big) 7 | std::cout << "big-endian\n"; 8 | else if constexpr (std::endian::native == std::endian::little) 9 | std::cout << "little-endian\n"; 10 | else 11 | std::cout << "mixed-endian\n"; 12 | 13 | return 0; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tests/multi_lang/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /multi_lang 6 | multi_lang.so 7 | multi_lang.dylib 8 | multi_lang.dll 9 | multi_lang.a 10 | multi_lang.lib 11 | multi_lang-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/multi_lang/c_dep/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c_dep", 3 | "language": "C" 4 | } -------------------------------------------------------------------------------- /tests/multi_lang/c_dep/source/another_dep.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void AnotherCFunction() 4 | { 5 | printf("Building multiple files from C\n"); 6 | } -------------------------------------------------------------------------------- /tests/multi_lang/c_dep/source/some_dep.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void CFunction() 4 | { 5 | printf("Hello from C and Redub\n"); 6 | } -------------------------------------------------------------------------------- /tests/multi_lang/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "hipreme" 4 | ], 5 | "dependencies": { 6 | "c_dep": {"path": "c_dep"} 7 | }, 8 | "description": "A minimal D application.", 9 | "license": "proprietary", 10 | "name": "multi_lang" 11 | } -------------------------------------------------------------------------------- /tests/multi_lang/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "multi_lang": {"path": "/home/hipreme/redub/tests/multi_lang"}, 5 | "c_dep": {"path": "/home/hipreme/redub/tests/multi_lang/c_dep"} 6 | } 7 | } -------------------------------------------------------------------------------- /tests/multi_lang/source/app.d: -------------------------------------------------------------------------------- 1 | 2 | import std.stdio; 3 | extern(C) void AnotherCFunction(); 4 | extern(C) void CFunction(); 5 | 6 | void main() 7 | { 8 | CFunction(); 9 | AnotherCFunction(); 10 | writeln("Edit source/app.d to start your project."); 11 | } 12 | -------------------------------------------------------------------------------- /tests/platforms/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /platforms 6 | platforms.so 7 | platforms.dylib 8 | platforms.dll 9 | platforms.a 10 | platforms.lib 11 | platforms-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/platforms/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "sourcePaths": ["source"], 6 | "configurations": [ 7 | { 8 | "name": "Linux", 9 | "platforms": ["linux"], 10 | "sourcePaths": ["extra/linux/"], 11 | "importPaths": ["extra/linux/"] 12 | }, 13 | { 14 | "name": "MacOS", 15 | "platforms": ["osx"], 16 | "sourcePaths": ["extra/osx/"], 17 | "importPaths": ["extra/osx/"] 18 | } 19 | ], 20 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 21 | "description": "A minimal D application.", 22 | "license": "proprietary", 23 | "name": "platforms" 24 | } -------------------------------------------------------------------------------- /tests/platforms/extra/linux/platform.d: -------------------------------------------------------------------------------- 1 | module platform; 2 | 3 | string getPlatformName(){return "linux";} -------------------------------------------------------------------------------- /tests/platforms/extra/osx/platform.d: -------------------------------------------------------------------------------- 1 | module platform; 2 | string getPlatformName(){return "MacOS";} -------------------------------------------------------------------------------- /tests/platforms/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import platform; 3 | void main() 4 | { 5 | writeln(getPlatformName); 6 | } 7 | -------------------------------------------------------------------------------- /tests/plugin_test/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /plugin_test 6 | plugin_test.so 7 | plugin_test.dylib 8 | plugin_test.dll 9 | plugin_test.a 10 | plugin_test.lib 11 | plugin_test-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | *.ilk -------------------------------------------------------------------------------- /tests/plugin_test/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo" 4 | ], 5 | "plugins": { 6 | "getmodules": "../../plugins/getmodules" 7 | }, 8 | "preBuildPlugins": { 9 | "getmodules": ["source/imports.txt"] 10 | }, 11 | 12 | "stringImportPaths": [ 13 | "source" 14 | ], 15 | "copyright": "Copyright © 2024, Marcelo", 16 | "description": "A minimal D application.", 17 | "license": "proprietary", 18 | "name": "plugin_test" 19 | } -------------------------------------------------------------------------------- /tests/plugin_test/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | string theImports = import("imports.txt"); 6 | 7 | writeln("Found imports: ", theImports); 8 | } 9 | -------------------------------------------------------------------------------- /tests/plugin_test/source/imports.txt: -------------------------------------------------------------------------------- 1 | app -------------------------------------------------------------------------------- /tests/preb/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /preb 6 | preb.so 7 | preb.dylib 8 | preb.dll 9 | preb.a 10 | preb.lib 11 | preb-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/preb/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "preBuildCommands": [ 6 | "cd other; dmd -c test.d" 7 | ], 8 | "importPaths": [ 9 | "other/" 10 | ], 11 | "sourceFiles": [ 12 | "other/test.o" 13 | ], 14 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 15 | "description": "A minimal D application.", 16 | "license": "proprietary", 17 | "name": "preb" 18 | } -------------------------------------------------------------------------------- /tests/preb/other/test.d: -------------------------------------------------------------------------------- 1 | module test; 2 | 3 | string getStringFromTest(){return "Hey, test";} -------------------------------------------------------------------------------- /tests/preb/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import test; 3 | 4 | void main() 5 | { 6 | writeln(getStringFromTest); 7 | } 8 | -------------------------------------------------------------------------------- /tests/redub_lib/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /redub_lib 6 | redub_lib.so 7 | redub_lib.dylib 8 | redub_lib.dll 9 | redub_lib.a 10 | redub_lib.lib 11 | redub_lib-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/redub_lib/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Hipreme" 4 | ], 5 | "copyright": "Copyright © 2024, Hipreme", 6 | "dependencies": { 7 | "redub": "~>1.2.0" 8 | }, 9 | "subConfigurations": { 10 | "redub": "library" 11 | }, 12 | "description": "A minimal D application.", 13 | "license": "proprietary", 14 | "name": "redub_lib" 15 | } -------------------------------------------------------------------------------- /tests/redub_lib/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "colorize": {"path":"../../../../AppData/Local/dub/packages/redub/1.2.0/redub/colorize"}, 5 | "redub": "1.2.0", 6 | "semver": {"path":"../../../../AppData/Local/dub/packages/redub/1.2.0/redub/semver"} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/redub_lib/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import redub.logging; 3 | import redub.api; 4 | 5 | void main() 6 | { 7 | import std.file; 8 | 9 | setLogLevel(LogLevel.verbose); 10 | 11 | ProjectDetails d = resolveDependencies( 12 | false, 13 | os, 14 | CompilationDetails.init, 15 | ProjectToParse(null, getcwd()) 16 | ); 17 | 18 | buildProject(d); 19 | } 20 | -------------------------------------------------------------------------------- /tests/sLib/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /s-lib 6 | s-lib.so 7 | s-lib.dylib 8 | s-lib.dll 9 | s-lib.a 10 | s-lib.lib 11 | s-lib-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/sLib/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s-lib", 3 | "dependencies": { 4 | "special": {"path": "special"} 5 | } 6 | } -------------------------------------------------------------------------------- /tests/sLib/source/app.d: -------------------------------------------------------------------------------- 1 | module app; 2 | import application; 3 | void main() 4 | { 5 | imported!"std.stdio".writeln(getSpecial); 6 | } -------------------------------------------------------------------------------- /tests/sLib/special/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "special", 3 | "targetType": "sourceLibrary" 4 | } -------------------------------------------------------------------------------- /tests/sLib/special/source/application.d: -------------------------------------------------------------------------------- 1 | module application; 2 | 3 | string getSpecial(){return "Kame";} -------------------------------------------------------------------------------- /tests/subcfg/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /subcfg 6 | subcfg.so 7 | subcfg.dylib 8 | subcfg.dll 9 | subcfg.a 10 | subcfg.lib 11 | subcfg-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/subcfg/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Hipreme" 4 | ], 5 | "sourcePaths": ["test"], 6 | "importPaths": ["test"], 7 | "subPackages": [ 8 | { 9 | "name": "core", 10 | "sourcePaths": [ "source" ], 11 | "importPaths": [ "source", "test"] 12 | } 13 | ], 14 | "dependencies": { 15 | ":core": "*" 16 | }, 17 | "copyright": "Copyright © 2024, Hipreme", 18 | "description": "A minimal D application.", 19 | "license": "proprietary", 20 | "name": "subcfg" 21 | } -------------------------------------------------------------------------------- /tests/subcfg/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main2() 4 | { 5 | writeln("Writeln from source"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/subcfg/test/app2.d: -------------------------------------------------------------------------------- 1 | module app2; 2 | import app; 3 | 4 | void main() 5 | { 6 | import std.stdio; 7 | main2(); 8 | writeln("Writeln from tests folder."); 9 | } -------------------------------------------------------------------------------- /tests/tarsd/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /tarsd 6 | tarsd.so 7 | tarsd.dylib 8 | tarsd.dll 9 | tarsd.a 10 | tarsd.lib 11 | tarsd-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/tarsd/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 6 | "dependencies": { 7 | "arsd-official:image_files": "~>11.3.0" 8 | }, 9 | "description": "A minimal D application.", 10 | "license": "proprietary", 11 | "name": "tarsd" 12 | } -------------------------------------------------------------------------------- /tests/tarsd/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "arsd-official": "11.3.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/tarsd/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | writeln("Edit source/app.d to start your project."); 6 | } 7 | -------------------------------------------------------------------------------- /tests/vec-bench/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /vec-bench 6 | vec-bench.so 7 | vec-bench.dylib 8 | vec-bench.dll 9 | vec-bench.a 10 | vec-bench.lib 11 | vec-bench-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/vec-bench/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 3 | "dependencies": { 4 | "dplug:core": "~>14.0" 5 | }, 6 | "name": "vec-bench" 7 | } -------------------------------------------------------------------------------- /tests/vec-bench/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "dplug": "14.2.1", 5 | "intel-intrinsics": "1.11.18" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/vec-bench/source/main.d: -------------------------------------------------------------------------------- 1 | module main; 2 | 3 | import std.stdio; 4 | 5 | void main() 6 | { 7 | writeln("Edit source/app.d to start your project."); 8 | } 9 | -------------------------------------------------------------------------------- /tests/vibehello/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /vibehello 6 | vibehello.so 7 | vibehello.dylib 8 | vibehello.dll 9 | vibehello.a 10 | vibehello.lib 11 | vibehello-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /tests/vibehello/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Marcelo Silva Nascimento Mancini" 4 | ], 5 | "copyright": "Copyright © 2024, Marcelo Silva Nascimento Mancini", 6 | "dependencies": { 7 | "vibe-d": "~>0.9.7" 8 | }, 9 | "description": "A minimal D application.", 10 | "license": "proprietary", 11 | "name": "vibehello" 12 | } -------------------------------------------------------------------------------- /tests/vibehello/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "diet-ng": "1.8.1", 5 | "eventcore": "0.9.27", 6 | "libasync": "0.8.6", 7 | "memutils": "1.0.10", 8 | "mir-linux-kernel": "1.0.1", 9 | "openssl": "3.3.3", 10 | "openssl-static": "1.0.2+3.0.8", 11 | "stdx-allocator": "2.77.5", 12 | "taggedalgebraic": "0.11.22", 13 | "vibe-container": "1.0.1", 14 | "vibe-core": "2.7.1", 15 | "vibe-d": "0.9.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/vibehello/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | writeln("Edit source/app.d to start your project."); 6 | } 7 | --------------------------------------------------------------------------------