diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..f7cb59e --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,34 @@ +# Documentation: https://docs.codecov.io/docs/codecov-yaml +# Validate with: `curl --data-binary @.codecov.yml https://codecov.io/validate` + +codecov: + notify: + # We don't want to wait for the CodeCov report + # See https://github.com/codecov/support/issues/312 + require_ci_to_pass: false + after_n_builds: 1 # send notifications after the first upload + wait_for_ci: false + + bot: dlang-bot + + # At Travis, the PR is merged into `master` before the testsuite is run. + # This allows CodeCov to adjust the resulting coverage diff, s.t. it matches + # with the GitHub diff. + # https://github.com/codecov/support/issues/363 + # https://docs.codecov.io/v4.3.6/docs/comparing-commits + allow_coverage_offsets: true + +coverage: + precision: 3 + round: down + range: "80...100" + + # Learn more at https://docs.codecov.io/docs/commit-status + status: + project: off + changes: off + patch: + default: + informational: true + +comment: false diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a5b705f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# https://help.github.com/en/articles/displaying-a-sponsor-button-in-your-repository + +open_collective: dlang +custom: https://dlang.org/foundation/donate.html diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..b3f6c17 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,21 @@ + + +### System information + +- **dub version**: (e.g. dub 1.3.0) +- **OS Platform and distribution**: (e.g. Windows 10, Linux Ubuntu 16.04) +- **compiler version** (e.g. dmd-2.074.1) + +### Bug Description + +### How to reproduce? + +### Expected Behavior + +### Logs diff --git a/.gitignore b/.gitignore index 15d535f..250273f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.obj +*~ .dub .directory @@ -12,8 +13,12 @@ /bin/dub /bin/__test__library-nonet__ /bin/__test__library__ +/bin/dub-test-library +/bin/libdub.a +/bin/dub-* # Ignore files or directories created by the test suite. +/test/test.log /test/custom-source-main-bug487/custom-source-main-bug487 /test/3-copyFiles/bin/ /test/ignore-hidden-1/ignore-hidden-1 @@ -25,6 +30,13 @@ /test/expected-issue616-output /test/describe-project/dummy.dat /test/describe-project/dummy-dep1.dat +/test/*/main/main +/test/*/*test-library +/test/*/*test-application +/test/*/exec-simple # Ignore coverage files cov/ + +# Ignore auto-generated docs +/docs diff --git a/.travis.yml b/.travis.yml index 2a6d10a..4b2fbd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,46 +1,130 @@ language: d +dist: trusty sudo: false -matrix: - include: - - d: dmd-2.072.0-b2 - env: [FRONTEND=2.072] - - d: dmd-2.071.2 - env: - - [FRONTEND=2.071] - - [COVERAGE=true] - - d: dmd-2.070.2 - env: [FRONTEND=2.070] - - d: dmd-2.069.2 - env: [FRONTEND=2.069] - - d: dmd-2.068.2 - env: [FRONTEND=2.068] - - d: dmd-2.067.1 - env: [FRONTEND=2.067] - - d: dmd-2.066.1 - env: [FRONTEND=2.066] - - d: dmd-2.065.0 - env: [FRONTEND=2.065] - - d: ldc-1.1.0-beta3 - env: [FRONTEND=2.071] - - d: ldc-1.0.0 - env: [FRONTEND=2.070] - - d: ldc-0.17.2 - env: [FRONTEND=2.068] - - d: ldc-0.16.1 - env: [FRONTEND=2.067] - - d: ldc-0.15.1 - env: [FRONTEND=2.066] - - d: ldc-0.14.0 - env: [FRONTEND=2.065] - - d: gdc-5.2.0 - env: [FRONTEND=2.066] - - d: gdc-4.9.2 - env: [FRONTEND=2.066] - - d: gdc-4.9.0 - env: [FRONTEND=2.065] - allow_failures: - - d: ldc-1.1.0-beta3 +addons: + apt: + packages: + - libevent-dev + +before_install: + # Use the dub-updating fork of the installer script until https://github.com/dlang/installer/pull/301 is merged + - wget https://raw.githubusercontent.com/wilzbach/installer-dub/master/script/install.sh -O ~/dlang/install.dub.sh + - . $(bash ~/dlang/install.dub.sh -a dub) + - dub --version script: - ./travis-ci.sh + +jobs: + allow_failures: + - d: gdc + include: + - stage: test + d: dmd-2.086.0 + env: [FRONTEND=2.086] + - d: dmd-2.081.1 + env: [FRONTEND=2.081] + - d: dmd-2.080.1 + env: [FRONTEND=2.080] + - d: dmd-2.079.1 + env: [FRONTEND=2.078] + - d: dmd-2.078.1 + env: [FRONTEND=2.078] + - d: dmd-2.077.1 + env: [FRONTEND=2.077, COVERAGE=true] + - d: dmd-2.076.1 + env: [FRONTEND=2.076] + - d: ldc-1.10.0 + env: [FRONTEND=2.080] + - d: ldc-1.9.0 + env: [FRONTEND=2.079] + - d: ldc-1.8.0 + env: [FRONTEND=2.078] + - d: ldc-1.7.0 + env: [FRONTEND=2.077] + - d: ldc-1.6.0 + env: [FRONTEND=2.076] + - stage: deploy + d: ldc-1.15.0 + os: osx + script: echo "Deploying to GitHub releases ..." && ./release.sh + deploy: + - provider: releases + file_glob: true + file: bin/dub-*.tar.gz + skip_cleanup: true + api_key: $GH_REPO_TOKEN + on: + tags: true + - d: ldc-1.15.0 + script: echo "Deploying to GitHub releases ..." && ./release.sh + env: [ARCH=32] + addons: + apt: + packages: + - g++-multilib + - libcurl4-openssl-dev:i386 + deploy: + - provider: releases + file_glob: true + file: bin/dub-*.tar.gz + skip_cleanup: true + api_key: $GH_REPO_TOKEN + on: + tags: true + - d: ldc-1.15.0 + script: echo "Deploying to GitHub releases ..." && ./release.sh + deploy: + - provider: releases + file_glob: true + file: bin/dub-*.tar.gz + skip_cleanup: true + api_key: $GH_REPO_TOKEN + on: + tags: true + - d: ldc-1.15.0 + script: echo "Deploying to GitHub releases (win32) ..." && ./release-windows.sh + addons: + apt: + packages: + - p7zip-full + deploy: + - provider: releases + file_glob: true + file: bin/dub-*.zip + skip_cleanup: true + api_key: $GH_REPO_TOKEN + on: + tags: true + - d: ldc-1.15.0 + script: echo "Deploying to GitHub releases (win64) ..." && ARCH=64 ./release-windows.sh + addons: + apt: + packages: + - p7zip-full + deploy: + - provider: releases + file_glob: true + file: bin/dub-*.zip + skip_cleanup: true + api_key: $GH_REPO_TOKEN + on: + tags: true + - stage: update-latest + script: echo "Deploying to GitHub pages ..." && mkdir -p docs && git describe --abbrev=0 --tags > docs/LATEST + deploy: + - provider: pages + skip_cleanup: true + local_dir: docs + github_token: $GH_REPO_TOKEN + on: + tags: true +stages: + - name: test + if: type = pull_request or (type = push and branch = master) + # Until deployment of the release binaries is fixed, always build them + #- name: deploy + #if: type = push and tag =~ ^v\d+\.\d+\.\d+[^-]*\$ # not a pre-release tag + - name: update-latest + if: type = push and tag =~ ^v\d+\.\d+\.\d+[^-]*\$ # not a pre-release tag diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..6c520e4 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,26 @@ +## Architecture + +![architecture](architecture.png) + +## Terminology + +
+
Package
+
A locally available version of a dub package, consisting of sources, binaries, and described by it's dub.sdl/json file.
+
PackageSupplier
+
A source to search and fetch package versions (zip bundles) from.
+
PackageManager
+
Responsible to manage packages (fetched or add-local packages), and overrides.
+
PackageRecipe
+
Abstract description of package sources, targets, configurations, and build settings.
+
Generator
+
Responsible for generating a build recipe (e.g. CMakeLists.txt, VS .sln) for a package, config, and build type. Direct builds (dmd, rdmd) are also implemented as generators. +
PackageDependency
+
Unresolved, abstract specification of a dependency, e.g. dependency "vibe-d" version="~>0.8.1".
+
DependencyResolver
+
Algorithm to resolve package dependencies to specific package versions (dub.selections.json), searching available package versions in package suppliers.
+
Target
+
A build output like a static library or executable.
+
BuildCache
+
Caches targets for a specific build id.
+
diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e7d8b..7df6cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,201 @@ Changelog ========= -v1.1.1 - 2016-11- +v1.12.0 - 2018-11-01 +------------------- +See + +v1.11.0 - 2018-09-01 +------------------- +See + +v1.10.0 - 2018-07-01 +------------------- +See + +v1.9.0 - 2018-05-01 +------------------- + +v1.8.1 - 2018-04-14 +------------------- + +- Fixed a regression in 1.8.0 that caused linker files specified as `sourceFiles` to not get inherited properly - [issue #1408][issue1408], [pull #1409][issue1409] +- Fixed a regression in 1.8.0 that caused `mainSourceFile` to be passed twice to the compiler on Windows - [issue #1407][issue1407], [pull #1410][issue1410] + +[issue1407]: https://github.com/dlang/dub/issues/1407 +[issue1408]: https://github.com/dlang/dub/issues/1408 +[issue1409]: https://github.com/dlang/dub/issues/1409 +[issue1410]: https://github.com/dlang/dub/issues/1410 + + +v1.8.0 - 2018-03-01 +------------------- + +v1.7.2 - 2018-02-07 +------------------- + +- more reliable retries and fallback mirror usage - [pull #1339][issue1339] + +[issue1339]: https://github.com/dlang/dub/issues/1339 + +v1.7.1 - 2018-01-21 +------------------- + +- Timeout requests to query mirror instead of hanging - [pull #1338][issue1338] + +[issue1338]: https://github.com/dlang/dub/issues/1338 + +v1.7.0 - 2018-01-01 +------------------- + +v1.6.0 - 2017-11-01 +------------------- + +- The version list displayed for interactive package removal is now sorted - [pull #1225][issue1225], [issue #1224][issue1224] +- File attributes of fetched packages are now preserved - [pull #1226][issue1226] +- `http://code-mirror.dlang.io` is now used as a fallback for `code.dlang.org` - [pull #1190][issue1190] +- Failed package downloads are now retried two more times (by Colin Grogan) - [pull #1198][issue1198] + +[issue1190]: https://github.com/dlang/dub/issues/1190 +[issue1198]: https://github.com/dlang/dub/issues/1198 +[issue1224]: https://github.com/dlang/dub/issues/1224 +[issue1225]: https://github.com/dlang/dub/issues/1225 +[issue1226]: https://github.com/dlang/dub/issues/1226 + + +v1.5.0 - 2017-09-01 +------------------- + +- Allow digits in package names (by Chad Joan) - [pull #1165][issue1165] +- Support the `no_proxy` environment variable for HTTP requests (by André Pany) - [pull #1162][issue1162], [issue #1159][issue1159] +- Read additional registry URLs (semicolon separated) from DUB_REGISTRY env var - [pull #1173][issue1173] +- Single file packages don't have to specify an explicit name anymore (will be inferred from the file name) - [pull #1081][issue1081] +- Add support for the `--parallel` switch for `dub test` (currently only has an effect for `--build-mode=singleFile`) - [pull #1182][issue1182] +- Improved error reporting for package download failures - [pull #1104][issue1104] +- Fixed building the code base against Android/Bionic - [pull #1202][issue1202] +- Extended the generated gitignore file (by Ryan Frame) - [pull #1050][issue1050] + +[issue1050]: https://github.com/dlang/dub/issues/1050 +[issue1081]: https://github.com/dlang/dub/issues/1081 +[issue1104]: https://github.com/dlang/dub/issues/1104 +[issue1182]: https://github.com/dlang/dub/issues/1182 +[issue1159]: https://github.com/dlang/dub/issues/1159 +[issue1162]: https://github.com/dlang/dub/issues/1162 +[issue1165]: https://github.com/dlang/dub/issues/1165 +[issue1173]: https://github.com/dlang/dub/issues/1173 +[issue1202]: https://github.com/dlang/dub/issues/1202 + + +v1.4.1 - 2017-08-10 +------------------- + +This release is identical with 1.4.0. + + +v1.4.0 - 2017-07-19 +------------------- + +- The copyright string is generated automatically by "dub init" +- "dub init" lets the user retry to enter the package name if not valid (by NotSpooky) - [pull #1122][issue1122] +- Improved collection speed for source/import files - [pull #1125][issue1125] +- Fixed "dub init" to allow digits in package names (by chadjoan) - [pull #1165][issue1165] +- Fixed a sub package build issue on Windows, where colons were used as part of the file name (by andre2007) - [issue #1130][issue1130], [pull #1137][issue1137] +- Fixed failures to acquire a lock when fetching packages - [pull #1149][issue1149] + +[issue1122]: https://github.com/dlang/dub/issues/1122 +[issue1125]: https://github.com/dlang/dub/issues/1125 +[issue1130]: https://github.com/dlang/dub/issues/1130 +[issue1137]: https://github.com/dlang/dub/issues/1137 +[issue1149]: https://github.com/dlang/dub/issues/1149 +[issue1165]: https://github.com/dlang/dub/issues/1165 + + +v1.3.0 - 2017-04-10 +------------------- + +### Features and improvements ### + +- Reduced the initialization time for "dub test" by several seconds by avoiding a complex regex - [pull #1078][issue1078] +- Reduced cubic runtime complexity for collecting string import files to almost linear in the common case - [pull #1079][issue1079] +- Compiler flag usage warnings are now only emitted for the root package, reducing build output noise - [a75023cd][commita75023cd] +- Avoid redundant recreation of hard links for build targets (by Danny Milosavljevic aka daym) - [pull #1071][issue1071] + +### Bug fixes ### + +- Fixed bogus rebuild of packages with no dependencies - [pull #1093][issue1093], [issue #1091][issue1091] +- Fixed building with vibe-core instead of vibe.d 0.7.x +- Fixed the VisualD generator to properly handle the "x86_mscoff" pseudoarchitecture - [4d416e73][commit4d416e73] + +[commita75023cd]: https://github.com/dlang/dub/commit/a75023cd050c055e81190bf7abc5793aba39852f +[commit4d416e73]: https://github.com/dlang/dub/commit/4d416e730df872b552ee1dcfa8340c224a4e51fc +[issue1071]: https://github.com/dlang/dub/issues/1071 +[issue1078]: https://github.com/dlang/dub/issues/1078 +[issue1079]: https://github.com/dlang/dub/issues/1079 +[issue1091]: https://github.com/dlang/dub/issues/1091 +[issue1093]: https://github.com/dlang/dub/issues/1093 + + +v1.2.2 - 2017-03-09 +------------------- + + +v1.2.1 - 2017-02-12 +------------------- + +### Bug fixes ### + +- Fixed compile error when compiling with vibe.d versions prior to 0.8.0 - [9d25e5dd][commit9d25e5dd] +- Fixed orphan format specifier error - [220b0128][commit220b0128] +- Fixed test executable name generation causing sub package test builds to fail - [e6262373][commite6262373] +- Fixed bogus warning message when compiling with `--arch x86_mscoff` (by Andrey Penechko aka MrSmith33) - [pull #1059][issue1059] +- Fixed plaform specifiers to work for "x86_mscoff" - [86e85276][commit86e85276] + +[commit9d25e5dd]: https://github.com/dlang/dub/commit/9d25e5dd0337e9c054ff32c4b921f32603d29293 +[commit220b0128]: https://github.com/dlang/dub/commit/220b01280041abbead57db6eec28e9279b9d7cf6 +[commite6262373]: https://github.com/dlang/dub/commit/e6262373558591fa8754704fcc2e8ddafabf6671 +[commit86e85276]: https://github.com/dlang/dub/commit/86e85276a7ff85c5cde4e1926c40e666a2b6bf78 +[issue1059]: https://github.com/dlang/dub/issues/1059 + + +v1.2.0 - 2017-01-22 +------------------- + +### Features and improvements ### + + - Added an `--override-config` command line option to force selecting specific configurations for dependencies - [pull #1004][issue1004] + - Added an `x86_mscoff` architecture corresponding to DMD's `-m32mscoff` flag (by John Colvin) - [pull #1007][issue1007] + - Implemented selective dependency upgrades ("dub upgrade ") - [issue #1024][issue1024] + - Multiple configurations with the same name are now detected and will cause a warning to be displayed - [issue #984][issue984] + - Updated the Sublime Text generator and the Bash completion script to include the default "release-debug" build type (by p0nce) - [pull #1028][issue1028] + - The `--force-remove` switch is scheduled for deprecation, as it didn't do anything for a while now - [pull #1023][issue1023] + +[issue984]: https://github.com/dlang/dub/issues/984 +[issue1004]: https://github.com/dlang/dub/issues/1004 +[issue1007]: https://github.com/dlang/dub/issues/1007 +[issue1023]: https://github.com/dlang/dub/issues/1023 +[issue1024]: https://github.com/dlang/dub/issues/1024 +[issue1028]: https://github.com/dlang/dub/issues/1028 + + +v1.1.2 - 2016-12-31 +------------------- + +### Bug fixes ### + + - Fixes configuration resolution in diamond dependency settings - [issue #1005][issue1005], [pull #1006][issue1006] + - Fixed path based package overrides ("dub add-override") - [issue #779][issue779] + - Contains various diagnostic and error message improvements - [issue #957][issue957], [pull #1010][issue1010], [pull #1012][issue1012], [issue #1019][issue1019] + +[issue779]: https://github.com/dlang/dub/issues/779 +[issue957]: https://github.com/dlang/dub/issues/957 +[issue1005]: https://github.com/dlang/dub/issues/1005 +[issue1006]: https://github.com/dlang/dub/issues/1006 +[issue1010]: https://github.com/dlang/dub/issues/1010 +[issue1012]: https://github.com/dlang/dub/issues/1012 +[issue1019]: https://github.com/dlang/dub/issues/1019 + + + v1.1.1 - 2016-11-30 ------------------- ### Bug fixes ### diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6a6705d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Guidelines for Contributing + +## Building + +Build a development version of dub using either `./build.sh` or `./build.cmd` on Windows. +When you already have a working dub binary, you can also just run `dub build`, though that won't update the version string. + +## Changelog + +Every feature addition should come with a changelog entry, see the [changelog/README.md](changelog/README.md) for how to add a new entry. Any `.dd` file is rendered using ddoc and is the same format used across all dlang repos. +For bugfixes make sure to automatically [close the issue via commit message](https://blog.github.com/2013-01-22-closing-issues-via-commit-messages/) (e.g. `fixes #123`), so that they can be listed in the changelog. diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e8aae00 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,3 @@ +#!/bin/env groovy +library 'dlang' +runPipeline() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4633bc3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2012-2016 RejectedSoftware e.K. +Copyright (c) 2016-2018 D Language Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 531c331..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2012-2016 RejectedSoftware e.K. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSE_DE.txt b/LICENSE_DE.txt deleted file mode 100644 index 01e9326..0000000 --- a/LICENSE_DE.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2012-2016 RejectedSoftware e.K. - -Hiermit wird unentgeltlich, jeder Person, die eine Kopie der Software und der zugeh�rigen Dokumentationen (die "Software") erh�lt, die Erlaubnis erteilt, uneingeschr�nkt zu benutzen, inklusive und ohne Ausnahme, dem Recht, sie zu verwenden, kopieren, �ndern, fusionieren, verlegen, verbreiten, unterlizenzieren und/oder zu verkaufen, und Personen, die diese Software erhalten, diese Rechte zu geben, unter den folgenden Bedingungen: - -Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen Kopien oder Teilkopien der Software beizulegen. - -DIE SOFTWARE WIRD OHNE JEDE AUSDR�CKLICHE ODER IMPLIZIERTE GARANTIE BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG F�R DEN VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK SOWIE JEGLICHER RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHR�NKT. IN KEINEM FALL SIND DIE AUTOREN ODER COPYRIGHTINHABER F�R JEGLICHEN SCHADEN ODER SONSTIGE ANSPR�CHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERF�LLUNG EINES VERTRAGES, EINES DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN. diff --git a/README.md b/README.md index 5bff591..030cf7a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ There is a central [package registry](https://github.com/dlang/dub-registry/) located at . -[![GitHub tag](https://img.shields.io/github/tag/dlang/dub.svg?maxAge=86400)](#) [![Build Status](https://travis-ci.org/dlang/dub.svg?branch=master)](https://travis-ci.org/dlang/dub) [![Coverage Status](https://coveralls.io/repos/dlang/dub/badge.svg)](https://coveralls.io/r/dlang/dub) +[![GitHub tag](https://img.shields.io/github/tag/dlang/dub.svg?maxAge=86400)](#) [![Travis](https://travis-ci.org/dlang/dub.svg?branch=master)](https://travis-ci.org/dlang/dub) [![Coverage Status](https://coveralls.io/repos/dlang/dub/badge.svg)](https://coveralls.io/r/dlang/dub) +[![Buildkite](https://badge.buildkite.com/c54d71c42284a042b9d578e28e093dff35f20cc8528319b1b6.svg?branch=master)](https://buildkite.com/dlang/dub) ## Introduction @@ -16,7 +17,7 @@ ## Key features - Simple package and build description not getting in your way - - Integrated with Git, avoiding maintainance tasks such as incrementing version numbers or uploading new project releases + - Integrated with Git, avoiding maintenance tasks such as incrementing version numbers or uploading new project releases - Generates VisualD project/solution files, integrated into MonoD - Support for DMD, GDC and LDC (common DMD flags are translated automatically) - Supports development workflows by optionally using local directories as a package source @@ -55,3 +56,12 @@ ## Using DUB as a library The [DUB package of DUB](http://code.dlang.org/packages/dub) can be used as a library to load or manipulate packages, or to resemble any functionality of the command line tool. The former task can be achieved by using the [Package class](https://github.com/dlang/dub/blob/master/source/dub/package_.d#L40). For examples on how to replicate the command line functionality, see [commandline.d](https://github.com/dlang/dub/blob/master/source/dub/commandline.d). + +## Minimal D compiler required to build DUB + +In general it is always recommended to build DUB with the latest version of your D compiler. +However, currently [2.076](https://dlang.org/changelog/2.076.0.html) is required to build DUB from source. + +# Contributing + +New contributers are always welcome, there's plenty to work on! For an easy start, take a look at issues marked [`bootcamp`](https://github.com/dlang/dub/labels/bootcamp) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9d5e983 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,123 @@ +platform: x64 +environment: + matrix: + #- DC: dmd + # DVersion: nightly + # arch: x64 + #- DC: dmd + # DVersion: nightly + # arch: x86 + - DC: dmd + DVersion: beta + arch: x64 + - DC: dmd + DVersion: beta + arch: x86 + - DC: dmd + DVersion: stable + arch: x64 + - DC: dmd + DVersion: 2.086.0 + arch: x86 + - DC: dmd + DVersion: 2.086.0 + arch: x64 + - DC: dmd + DVersion: stable + arch: x86 + - DC: ldc + DVersion: stable + arch: x64 + - DC: ldc + DVersion: 1.15.0 + arch: x64 + +skip_tags: false +branches: + only: + - master + - stable + +install: + - ps: function ResolveLatestDMD + { + $version = $env:DVersion; + if($version -eq "stable") { + $latest = (Invoke-WebRequest "http://downloads.dlang.org/releases/LATEST").toString(); + $url = "http://downloads.dlang.org/releases/2.x/$($latest)/dmd.$($latest).windows.7z"; + }elseif($version -eq "beta") { + $latest = (Invoke-WebRequest "http://downloads.dlang.org/pre-releases/LATEST").toString(); + $latestVersion = $latest.split("-")[0].split("~")[0]; + $url = "http://downloads.dlang.org/pre-releases/2.x/$($latestVersion)/dmd.$($latest).windows.7z"; + }elseif($version -eq "nightly") { + $url = "http://nightlies.dlang.org/dmd-master-2017-05-20/dmd.master.windows.7z" + }else { + $url = "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z"; + } + $env:PATH += ";C:\dmd2\windows\bin;"; + return $url; + } + - ps: function ResolveLatestLDC + { + $version = $env:DVersion; + $arch = $env:arch; + if($version -eq "stable") { + $latest = (Invoke-WebRequest "https://ldc-developers.github.io/LATEST").toString().replace("`n","").replace("`r",""); + $url = "https://github.com/ldc-developers/ldc/releases/download/v$($latest)/ldc2-$($latest)-windows-$($arch).7z"; + }elseif($version -eq "beta") { + $latest = (Invoke-WebRequest "https://ldc-developers.github.io/LATEST_BETA").toString().replace("`n","").replace("`r",""); + $url = "https://github.com/ldc-developers/ldc/releases/download/v$($latest)/ldc2-$($latest)-windows-$($arch).7z"; + } else { + $latest = $version; + $url = "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-windows-$($arch).7z"; + } + $env:PATH += ";C:\ldc2-$($latest)-windows-$($arch)\bin"; + $env:DC = "ldc2"; + return $url; + } + - ps: function SetUpDCompiler + { + $env:toolchain = "msvc"; + if($env:DC -eq "dmd"){ + echo "downloading ..."; + $url = ResolveLatestDMD; + echo $url; + Invoke-WebRequest $url -OutFile "c:\dmd.7z"; + echo "finished."; + pushd c:\\; + 7z x dmd.7z > $null; + popd; + } + elseif($env:DC -eq "ldc"){ + echo "downloading ..."; + $url = ResolveLatestLDC; + echo $url; + Invoke-WebRequest $url -OutFile "c:\ldc.zip"; + echo "finished."; + pushd c:\\; + 7z x ldc.zip > $null; + popd; + } + } + - ps: SetUpDCompiler + +build_script: + - ps: if($env:arch -eq "x86"){ + $env:compilersetupargs = "x86"; + $env:Darch = "x86"; + $env:DConf = "m32"; + }elseif($env:arch -eq "x64"){ + $env:compilersetupargs = "amd64"; + $env:Darch = "x86_64"; + $env:DConf = "m64"; + } + - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall"; + - '"%compilersetup%" %compilersetupargs%' + +test_script: + - echo %PLATFORM% + - echo %Darch% + - echo %DC% + - echo %PATH% + - '%DC% --version' + - dub test --arch=%Darch% --compiler=%DC% diff --git a/architecture.graphmlz b/architecture.graphmlz new file mode 100644 index 0000000..73716a1 --- /dev/null +++ b/architecture.graphmlz Binary files differ diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000..a66bff5 --- /dev/null +++ b/architecture.png Binary files differ diff --git a/build-files.txt b/build-files.txt index 3d48908..fbda121 100644 --- a/build-files.txt +++ b/build-files.txt @@ -7,6 +7,12 @@ source/dub/init.d source/dub/packagemanager.d source/dub/packagesupplier.d +source/dub/packagesuppliers/package.d +source/dub/packagesuppliers/fallback.d +source/dub/packagesuppliers/filesystem.d +source/dub/packagesuppliers/packagesupplier.d +source/dub/packagesuppliers/maven.d +source/dub/packagesuppliers/registry.d source/dub/package_.d source/dub/platform.d source/dub/project.d diff --git a/build-gdc.sh b/build-gdc.sh index 1924b08..df0e5ce 100755 --- a/build-gdc.sh +++ b/build-gdc.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -e if [ "$GDC" = "" ]; then @@ -17,7 +17,7 @@ echo "enum dubVersion = \"$GITVER\";" >> source/dub/version_.d echo Running $GDC... -$GDC -obin/dub -lcurl -w -fversion=DubUseCurl -Isource $* $LIBS @build-files.txt +$GDC -obin/dub -lcurl -w -fversion=DubUseCurl -fversion=DubApplication -Isource $* $LIBS @build-files.txt echo DUB has been built as bin/dub. echo echo You may want to run diff --git a/build.cmd b/build.cmd index f965194..f6d6a34 100644 --- a/build.cmd +++ b/build.cmd @@ -7,7 +7,7 @@ @echo enum dubVersion = "%GITVER%"; >> source\dub\version_.d @echo Executing %DC%... -@%DC% -ofbin\dub.exe -g -debug -w -version=DubUseCurl -Isource curl.lib %* @build-files.txt +@%DC% -ofbin\dub.exe -g -debug -w -version=DubUseCurl -version=DubApplication -Isource curl.lib %* @build-files.txt @if errorlevel 1 exit /b 1 @echo DUB has been built. You probably also want to add the following entry to your diff --git a/build.sh b/build.sh index 81823e2..ba9fffc 100755 --- a/build.sh +++ b/build.sh @@ -16,42 +16,48 @@ exit 1 fi -VERSION=$($DMD --version 2>/dev/null | sed -n 's|DMD.* v||p') +VERSION=$($DMD --version 2>/dev/null | sed -En 's|.*DMD.* v([[:digit:]\.]+).*|\1|p') +# workaround for link order issues with libcurl (phobos needs to come before curl) if [[ $VERSION < 2.069.0 ]]; then - # link against libcurl - LIBS=`pkg-config --libs libcurl 2>/dev/null || echo "-lcurl"` -fi + # link against libcurl + LIBS=`pkg-config --libs libcurl 2>/dev/null || echo "-lcurl"` -# fix for modern GCC versions with --as-needed by default -if [[ `$DMD --help | head -n1 | grep 'DMD\(32\|64\)'` ]]; then - if [ `uname` = "Linux" ]; then - LIBS="-l:libphobos2.a $LIBS" - else - LIBS="-lphobos2 $LIBS" + # fix for modern GCC versions with --as-needed by default + if [[ `$DMD --help | head -n1 | grep 'DMD\(32\|64\)'` ]]; then + if [ `uname` = "Linux" ]; then + LIBS="-l:libphobos2.a $LIBS" + else + LIBS="-lphobos2 $LIBS" + fi + elif [[ `$DMD --help | head -n1 | grep '^LDC '` ]]; then + if [ `uname` = "SunOS" ]; then + LIBS="-lnsl -lsocket -lphobos2-ldc $LIBS" + else + LIBS="-lphobos2-ldc $LIBS" + fi fi -elif [[ `$DMD --help | head -n1 | grep '^LDC '` ]]; then - if [ `uname` = "SunOS" ]; then - LIBS="-lnsl -lsocket -lphobos2-ldc $LIBS" - else - LIBS="-lphobos2-ldc $LIBS" - fi + + # adjust linker flags for dmd command line + LIBS=`echo "$LIBS" | sed 's/^-L/-L-L/; s/ -L/ -L-L/g; s/^-l/-L-l/; s/ -l/ -L-l/g'` fi -# adjust linker flags for dmd command line -LIBS=`echo "$LIBS" | sed 's/^-L/-L-L/; s/ -L/ -L-L/g; s/^-l/-L-l/; s/ -l/ -L-l/g'` - -echo Generating version file... -if [ "$GITVER" = "" ]; then - GITVER=$(git describe) || GITVER=unknown +if [ "$GITVER" = "" ]; then + GITVER=$(git describe) || echo "Could not determine a version with git." fi -echo "module dub.version_;" > source/dub/version_.d -echo "enum dubVersion = \"$GITVER\";" >> source/dub/version_.d +if [ "$GITVER" != "" ]; then + echo Generating version file... + echo "module dub.version_;" > source/dub/version_.d + echo "enum dubVersion = \"$GITVER\";" >> source/dub/version_.d +else + echo Using existing version file. +fi -# For OSX compatibility >= 10.7 -MACOSX_DEPLOYMENT_TARGET=10.7 +# For OSX compatibility >= 10.8 +MACOSX_DEPLOYMENT_TARGET=10.8 echo Running $DMD... -$DMD -ofbin/dub -w -version=DubUseCurl -Isource $* $LIBS @build-files.txt +$DMD -ofbin/dub -g -O -w -version=DubUseCurl -version=DubApplication -Isource $* $LIBS @build-files.txt +bin/dub --version echo DUB has been built as bin/dub. echo echo You may want to run diff --git a/changelog/README.md b/changelog/README.md new file mode 100644 index 0000000..874d70f --- /dev/null +++ b/changelog/README.md @@ -0,0 +1,42 @@ +This directory will get copied to dlang.org and cleared when master gets +merged into stable prior to a new release. + +How to add a new changelog entry to the pending changelog? +========================================================== + +Create a new file in the `changelog` folder. It should end with `.dd` and look +similar to a git commit message. The first line represents the title of the change. +After an empty line follows the long description: + +``` +My fancy title of the new feature + +A long description of the new feature in `std.range`. +It can be followed by an example: +------- +import std.range : padLeft, padRight; +import std.algorithm.comparison : equal; + +assert([1, 2, 3, 4, 5].padLeft(0, 7).equal([0, 0, 1, 2, 3, 4, 5])); + +assert("Hello World!".padRight('!', 15).equal("Hello World!!!!")); +------- +and links to the documentation, e.g. $(REF drop, std, range) or +$(REF_ALTTEXT a custom name for the function, drop, std, range). + +Links to the spec can look like this $(LINK2 $(ROOT_DIR)spec/module.html, this) +and of course you can link to other $(LINK2 https://forum.dlang.org/, external resources). +``` + +The title can't contain links (it's already one). +For more infos, see the [Ddoc spec](https://dlang.org/spec/ddoc.html). + +Preview changes +--------------- + +If you have cloned the [tools](https://github.com/dlang/tools) and [dlang.org](https://github.com/dlang/dlang.org) repo, +you can preview the changelog with: + +``` +make -C ../dlang.org -f posix.mak pending_changelog +``` diff --git a/dub.sdl b/dub.sdl index 76cadd4..3f17241 100644 --- a/dub.sdl +++ b/dub.sdl @@ -1,6 +1,7 @@ name "dub" description "Package manager for D packages" -authors "Matthias Dondorff" "Sönke Ludwig" +authors "Sönke Ludwig" "Martin Nowak" "Matthias Dondorff" "Sebastian Wilzbach" \ + "more than 80 contributors total" copyright "Copyright © 2012-2016 rejectedsoftware e.K., Copyright © 2012-2014 Matthias Dondorff" license "MIT" @@ -10,25 +11,24 @@ targetType "executable" mainSourceFile "source/app.d" libs "curl" - versions "DubUseCurl" + versions "DubUseCurl" "DubApplication" } configuration "library" { targetType "library" - libs "curl" excludedSourceFiles "source/app.d" copyFiles "bin/libcurl.dll" "bin/libeay32.dll" "bin/ssleay32.dll" platform="windows" versions "DubUseCurl" } configuration "library-nonet" { - dependency "vibe-d" version="~>0.7.29" optional=true + dependency "vibe-d:http" version=">=0.7.30 <=0.9.0" optional=true targetType "library" excludedSourceFiles "source/app.d" } configuration "dynamic-library-nonet" { - dependency "vibe-d" version="~>0.7.29" optional=true + dependency "vibe-d:http" version=">=0.7.30 <=0.9.0" optional=true targetType "dynamicLibrary" excludedSourceFiles "source/app.d" } diff --git a/dub.selections.json b/dub.selections.json index 340d089..5ef0425 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,11 +1,19 @@ { "fileVersion": 1, "versions": { - "libasync": "0.7.9", + "botan": "1.12.10", + "botan-math": "1.0.3", + "diet-ng": "1.5.0", + "eventcore": "0.8.41", + "libasync": "0.8.4", "libev": "5.0.0+4.04", - "libevent": "2.0.1+2.0.16", - "memutils": "0.4.8", - "openssl": "1.1.4+1.0.1g", - "vibe-d": "0.7.30" + "libevent": "2.0.2+2.0.16", + "memutils": "0.4.13", + "mir-linux-kernel": "1.0.1", + "openssl": "1.1.6+1.0.1g", + "stdx-allocator": "2.77.5", + "taggedalgebraic": "0.10.13", + "vibe-core": "1.6.0", + "vibe-d": "0.8.4" } } diff --git a/release-windows.sh b/release-windows.sh new file mode 100755 index 0000000..0deab6b --- /dev/null +++ b/release-windows.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Build the Windows binaries under Linux +set -eux -o pipefail + +BIN_NAME=dub + +# Allow the script to be run from anywhere +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $DIR + +source setup-ldc-windows.sh + +# Run LDC with cross-compilation +archiveName="$BIN_NAME-$VERSION-$OS-$ARCH_SUFFIX.zip" +echo "Building $archiveName" +mkdir -p bin +DC=ldmd2 DFLAGS="-release" ./build.sh + +cd bin +mv "${BIN_NAME}" "${BIN_NAME}.exe" +zip "$archiveName" "${BIN_NAME}.exe" diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..bbf6736 --- /dev/null +++ b/release.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -eux -o pipefail + +VERSION=$(git describe --abbrev=0 --tags) +ARCH="${ARCH:-64}" +CUSTOM_FLAGS=() +unameOut="$(uname -s)" +case "$unameOut" in + Linux*) + OS=linux + CUSTOM_FLAGS+=("-L--export-dynamic") + ;; + Darwin*) + OS=osx + CUSTOM_FLAGS+=("-L-dead_strip") + ;; + *) echo "Unknown OS: $unameOut"; exit 1 +esac + +if [[ $(basename "$DMD") =~ ldmd.* ]] ; then + CUSTOM_FLAGS+=("-flto=full") + # ld.gold is required on Linux + if [ ${OS:-} == "linux" ] ; then + CUSTOM_FLAGS+=("-linker=gold") + fi +fi + +case "$ARCH" in + 64) ARCH_SUFFIX="x86_64";; + 32) ARCH_SUFFIX="x86";; + *) echo "Unknown ARCH: $ARCH"; exit 1 +esac + +archiveName="dub-$VERSION-$OS-$ARCH_SUFFIX.tar.gz" + +echo "Building $archiveName" +DFLAGS="-release -m$ARCH ${CUSTOM_FLAGS[@]}" DMD="$(command -v $DMD)" ./build.sh +tar cvfz "bin/$archiveName" -C bin dub diff --git a/scripts/fish-completion/dub.fish b/scripts/fish-completion/dub.fish index 1207484..c8cd222 100644 --- a/scripts/fish-completion/dub.fish +++ b/scripts/fish-completion/dub.fish @@ -49,7 +49,7 @@ complete -c dub -n "contains '$cmd' (commandline -poc)" -s a -l arch -r -d "Force architecture" complete -c dub -n "contains '$cmd' (commandline -poc)" -s d -l debug -r -d "Debug identifier" complete -c dub -n "contains '$cmd' (commandline -poc)" -l nodeps -d "No dependency check" - complete -c dub -n "contains '$cmd' (commandline -poc)" -s b -l build -u -x -d "Build type" -a "debug plain release release-nobounds unittest profile docs ddox cov unittest-cov" + complete -c dub -n "contains '$cmd' (commandline -poc)" -s b -l build -u -x -d "Build type" -a "debug plain release release-debug release-nobounds unittest profile profile-gc docs ddox cov unittest-cov syntax" complete -c dub -n "contains '$cmd' (commandline -poc)" -l build-mode -x -d "How compiler & linker are invoked" -a "separate allAtOnce singleFile" complete -c dub -n "contains '$cmd' (commandline -poc)" -l compiler -x -d "Compiler binary" -a "dmd gdc ldc gdmd ldmd" end diff --git a/scripts/man/.gitignore b/scripts/man/.gitignore new file mode 100644 index 0000000..2c2a370 --- /dev/null +++ b/scripts/man/.gitignore @@ -0,0 +1,2 @@ +*.1 +/gen_man diff --git a/scripts/man/README.md b/scripts/man/README.md new file mode 100644 index 0000000..1b7484a --- /dev/null +++ b/scripts/man/README.md @@ -0,0 +1,13 @@ +1) Build +-------- + +```shell +./gen_man.d +``` + +2) Preview +---------- + +```shell +man -l dub.1 +``` diff --git a/scripts/man/gen_man.d b/scripts/man/gen_man.d new file mode 100755 index 0000000..d9625be --- /dev/null +++ b/scripts/man/gen_man.d @@ -0,0 +1,187 @@ +#!/usr/bin/env dub +/+dub.sdl: +dependency "dub" path="../.." ++/ + +import std.algorithm, std.conv, std.format, std.path, std.range, std.stdio; +import dub.commandline; + +string italic(string w) +{ + return `\fI` ~ w ~ `\fR`; +} + +string bold(string w) +{ + return `\fB` ~ w ~ `\fR`; +} + +string header(string heading) +{ + return ".SH " ~ heading; +} + +string br(string s) +{ + return ".BR " ~ s; +} + +struct Config +{ + import std.datetime; + SysTime date; + + static Config init(){ + import std.process : environment; + Config config; + config.date = Clock.currTime; + auto diffable = environment.get("DIFFABLE", "0"); + if (diffable == "1") + config.date = SysTime(DateTime(2018, 01, 01)); + + config.cwd = __FILE_FULL_PATH__.dirName; + return config; + } + string cwd; +} + +void writeHeader(ref File manFile, string manName, const Config config) +{ + static immutable manHeader = +`.TH %s 1 "%s" "The D Language Foundation" "The D Language Foundation" +.SH NAME`; + manFile.writefln(manHeader, manName, config.date.toISOExtString.take(10)); +} + +void writeFooter(ref File manFile, string seeAlso, const Config config) +{ + static immutable manFooter = +`.SH FILES +\fIdub\&.sdl\fR, \fIdub\&.json\fR +.SH AUTHOR +Copyright (c) 1999-%s by The D Language Foundation +.SH "ONLINE DOCUMENTATION" +.UR http://code.dlang.org/docs/commandline +.UE http://code.dlang.org/docs/commandline +.SH "SEE ALSO" +%s`; + manFile.writefln(manFooter, config.date.year, seeAlso); +} + +void writeMainManFile(CommandArgs args, CommandGroup[] commands, + string fileName, const Config config) +{ + auto manFile = File(config.cwd.buildPath(fileName), "w"); + manFile.writeHeader("DUB", config); + auto seeAlso = ["dmd(1)".br, "rdmd(1)"].joiner("\n").to!string; + scope(exit) manFile.writeFooter(seeAlso, config); + + alias writeln = (m) => manFile.writeln(m); + writeln(`dub \- Package and build management system for D`); + writeln("SYNOPSIS".header); + writeln(`.B dub +[\-\-version] +[\fICOMMAND\fR] +[\fIOPTIONS\&.\&.\&.\fR] +[\-\- [\fIAPPLICATION ARGUMENTS\&.\&.\&.\fR]]`); + + writeln("DESCRIPTION".header); + writeln(`Manages the DUB project in the current directory\&. DUB can serve as a build +system and a package manager, automatically keeping track of project's +dependencies \- both downloading them and linking them into the application.`); + + writeln(".SH COMMANDS"); + foreach (grp; commands) { + foreach (cmd; grp.commands) { + writeln(".TP"); + writeln(cmd.name.bold); + writeln(cmd.helpText.joiner("\n")); + } + } + + writeln("COMMON OPTIONS".header); + args.writeArgs(manFile); +} + +string highlightArguments(string args) +{ + import std.regex : regex, replaceAll; + static auto re = regex("<([^>]*)>"); + static const reReplacement = "<%s>".format(`$1`.italic); + return args.replaceAll(re, reReplacement); +} + +void writeArgs(CommandArgs args, ref File manFile) +{ + alias write = (m) => manFile.write(m); + foreach (arg; args.recognizedArgs) + { + auto names = arg.names.split("|"); + assert(names.length == 1 || names.length == 2); + string sarg = names[0].length == 1 ? names[0] : null; + string larg = names[0].length > 1 ? names[0] : names.length > 1 ? names[1] : null; + write(".IP "); + if (sarg !is null) { + write("-%s".format(sarg)); + if (larg !is null) + write(", "); + } + if (larg !is null) { + write("--%s".format(larg)); + if (!arg.defaultValue.peek!bool) + write("=VALUE"); + } + manFile.writeln; + manFile.writeln(arg.helpText.join("\n")); + } +} + +void writeManFile(Command command, const Config config) +{ + import std.uni : toUpper; + + auto args = new CommandArgs(null); + command.prepare(args); + string fileName = format("dub-%s.1", command.name); + auto manFile = File(config.cwd.buildPath(fileName), "w"); + auto manName = format("DUB-%s", command.name).toUpper; + manFile.writeHeader(manName, config); + static immutable seeAlso = ["dmd(1)".br, "dub(1)"].joiner("\n").to!string; + scope(exit) manFile.writeFooter(seeAlso, config); + + alias writeln = (m) => manFile.writeln(m); + writeln(`dub \- Package and build management system for D`); + + writeln("SYNOPSIS".header); + writeln("dub %s".format(command.name).bold); + writeln(command.argumentsPattern.highlightArguments); + writeln(`OPTIONS\&.\&.\&.`.italic); + if (command.acceptsAppArgs) + { + writeln("[-- <%s>]".format("application arguments...".italic)); + } + + writeln("DESCRIPTION".header); + writeln(command.helpText.joiner("\n\n")); + writeln("OPTIONS".header); + args.writeArgs(manFile); +} + +void main() +{ + Config config = Config.init; + auto commands = getCommands(); + + // main dub.1 + { + CommonOptions options; + auto args = new CommandArgs(null); + options.prepare(args); + args.writeMainManFile(commands, "dub.1", config); + } + + // options for each specific command + foreach (cmd; commands.map!(a => a.commands).joiner) { + cmd.writeManFile(config); + } +} diff --git a/scripts/rpm-package/make_installer.sh b/scripts/rpm-package/make_installer.sh index eb2ab86..097e9c4 100755 --- a/scripts/rpm-package/make_installer.sh +++ b/scripts/rpm-package/make_installer.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -e cd ../../ DUB_PATH=`pwd` diff --git a/scripts/win-installer/EnvVarUpdate.nsh b/scripts/win-installer/EnvVarUpdate.nsh index b67e3ba..245499b 100644 --- a/scripts/win-installer/EnvVarUpdate.nsh +++ b/scripts/win-installer/EnvVarUpdate.nsh @@ -145,7 +145,7 @@ Goto EnvVarUpdate_Restore_Vars ${EndIf} - ;;khc - here check if length is going to be greater then max string length + ;;khc - here check if length is going to be greater than max string length ;; and abort if so - also abort if original path empty - may mean ;; it was too long as well- write message to say set it by hand diff --git a/semaphore-ci.sh b/semaphore-ci.sh new file mode 100755 index 0000000..a415466 --- /dev/null +++ b/semaphore-ci.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -euo pipefail +set -x + +if [ "${D_VERSION:-dmd}" == "gdc" ] ; then + + # Use the dub-updating fork of the installer script until https://github.com/dlang/installer/pull/301 is merged + wget https://raw.githubusercontent.com/wilzbach/installer-dub/master/script/install.sh -O install.dub.sh + bash install.dub.sh -a dub + dub_path_activate="$(find $HOME/dlang/*/activate | head -1)" + rm "${dub_path_activate}" + dub_path="$(dirname "$dub_path_activate")" + sudo ln -s "${dub_path}/dub" /usr/bin/dub + + export DMD=gdmd + export DC=gdc + # It's technically ~"2.076", but Ternary doesn't seem to have been ported and Vibe.d seems to depend on this. + # Ternary was added in 2.072: https://dlang.org/phobos/std_typecons.html#.Ternary + # However, the nonet tests is done only for > 2.072 + export FRONTEND=2.072 + + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install -y gdc-8 + # fetch the dmd-like wrapper + sudo wget https://raw.githubusercontent.com/D-Programming-GDC/GDMD/master/dmd-script -O /usr/bin/gdmd + sudo chmod +x /usr/bin/gdmd + # DUB requires gdmd + sudo ln -s /usr/bin/gdc-8 /usr/bin/gdc + # fake install script and create a fake 'activate' script + mkdir -p ~/dlang/gdc-8 + echo "deactivate(){ echo;}" > ~/dlang/gdc-8/activate + +else + curl --connect-timeout 5 --max-time 10 --retry 5 --retry-delay 1 --retry-max-time 60 https://dlang.org/install.sh | bash -s "$D_VERSION" +fi + +./travis-ci.sh diff --git a/setup-ldc-windows.sh b/setup-ldc-windows.sh new file mode 100644 index 0000000..0656b5a --- /dev/null +++ b/setup-ldc-windows.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +# sets up LDC for cross-compilation. Source this script, s.t. the new LDC is in PATH + +LDC_VERSION="1.13.0" +ARCH=${ARCH:-32} +VERSION=$(git describe --abbrev=0 --tags) +OS=windows + +# Step 0: install ldc +if [ ! -f install.sh ] ; then + wget https://dlang.org/install.sh +fi +. $(bash ./install.sh -a "ldc-${LDC_VERSION}") + +# for the install.sh script only +LDC_PATH="$(dirname $(dirname $(which ldc2)))" + +# Step 1a: download the LDC x64 windows binaries +if [ "${ARCH}" == 64 ] && [ ! -d "ldc2-${LDC_VERSION}-windows-x64" ] ; then + wget "https://github.com/ldc-developers/ldc/releases/download/v1.13.0/ldc2-${LDC_VERSION}-windows-x64.7z" + 7z x "ldc2-${LDC_VERSION}-windows-x64.7z" > /dev/null + # Step 2a: Add LDC windows binaries to LDC Linux + if [ ! -d "${LDC_PATH}/lib-win64" ] ; then + cp -r ldc2-1.13.0-windows-x64/lib "${LDC_PATH}/lib-win64" + cat >> "$LDC_PATH"/etc/ldc2.conf < /dev/null + # Step 2b: Add LDC windows binaries to LDC Linux + if [ ! -d "${LDC_PATH}/lib-win32" ] ; then + cp -r ldc2-1.13.0-windows-x86/lib "${LDC_PATH}/lib-win32" + cat >> "$LDC_PATH"/etc/ldc2.conf <= 2 && args[1] == "-") + { + auto path = getTempFile("app", ".d"); + stdin.byChunk(4096).joiner.toFile(path.toNativeString()); + args = args[0] ~ [path.toNativeString()] ~ args[2..$]; + } + + // create the list of all supported commands + CommandGroup[] commands = getCommands(); + string[] commandNames = commands.map!(g => g.commands.map!(c => c.name).array).join.array; + + // Shebang syntax support for files without .d extension + if (args.length >= 2 && !args[1].endsWith(".d") && !args[1].startsWith("-") && !commandNames.canFind(args[1])) { + if (exists(args[1])) { + auto path = getTempFile("app", ".d"); + copy(args[1], path.toNativeString()); + args[1] = path.toNativeString(); + } else if (exists(args[1].setExtension(".d"))) { + args[1] = args[1].setExtension(".d"); + } + } + // special single-file package shebang syntax if (args.length >= 2 && args[1].endsWith(".d")) { args = args[0] ~ ["run", "-q", "--temp-build", "--single", args[1], "--"] ~ args[2 ..$]; @@ -126,7 +148,6 @@ // parse general options CommonOptions options; LogLevel loglevel = LogLevel.info; - options.root_path = getcwd(); auto common_args = new CommandArgs(args); try { @@ -136,6 +157,7 @@ else if (options.verbose) loglevel = LogLevel.diagnostic; else if (options.vquiet) loglevel = LogLevel.none; else if (options.quiet) loglevel = LogLevel.warn; + else if (options.verror) loglevel = LogLevel.error; setLogLevel(loglevel); } catch (Throwable e) { logError("Error processing arguments: %s", e.msg); @@ -144,8 +166,14 @@ return 1; } - // create the list of all supported commands - CommandGroup[] commands = getCommands(); + if (options.root_path.empty) + options.root_path = getcwd(); + else + { + import std.path : absolutePath, buildNormalizedPath; + + options.root_path = options.root_path.absolutePath.buildNormalizedPath; + } // extract the command string cmdname; @@ -211,19 +239,32 @@ // initialize the root package if (!cmd.skipDubInitialization) { if (options.bare) { - dub = new Dub(Path(getcwd())); + dub = new Dub(NativePath(getcwd())); + dub.rootPath = NativePath(options.root_path); dub.defaultPlacementLocation = options.placementLocation; } else { // initialize DUB - auto package_suppliers = options.registry_urls.map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url))).array; + auto package_suppliers = options.registry_urls + .map!((url) { + // Allow to specify fallback mirrors as space separated urls. Undocumented as we + // should simply retry over all registries instead of using a special + // FallbackPackageSupplier. + auto urls = url.splitter(' '); + PackageSupplier ps = getRegistryPackageSupplier(urls.front); + urls.popFront; + if (!urls.empty) + ps = new FallbackPackageSupplier(ps ~ urls.map!getRegistryPackageSupplier.array); + return ps; + }) + .array; dub = new Dub(options.root_path, package_suppliers, options.skipRegistry); dub.dryRun = options.annotate; dub.defaultPlacementLocation = options.placementLocation; // make the CWD package available so that for example sub packages can reference their // parent package. - try dub.packageManager.getOrLoadPackage(Path(options.root_path)); - catch (Exception e) { logDiagnostic("No package found in current working directory."); } + try dub.packageManager.getOrLoadPackage(NativePath(options.root_path)); + catch (Exception e) { logDiagnostic("No valid package found in current working directory: %s", e.msg); } } } @@ -246,7 +287,7 @@ /** Contains and parses options common to all commands. */ struct CommonOptions { - bool verbose, vverbose, quiet, vquiet; + bool verbose, vverbose, quiet, vquiet, verror; bool help, annotate, bare; string[] registry_urls; string root_path; @@ -258,18 +299,24 @@ { args.getopt("h|help", &help, ["Display general or command specific help"]); args.getopt("root", &root_path, ["Path to operate in instead of the current working dir"]); - args.getopt("registry", ®istry_urls, ["Search the given DUB registry URL first when resolving dependencies. Can be specified multiple times."]); + args.getopt("registry", ®istry_urls, [ + "Search the given registry URL first when resolving dependencies. Can be specified multiple times. Available registry types:", + " DUB: URL to DUB registry (default)", + " Maven: URL to Maven repository + group id containing dub packages as artifacts. E.g. mvn+http://localhost:8040/maven/libs-release/dubpackages", + ]); args.getopt("skip-registry", &skipRegistry, [ - "Skips searching certain package registries for dependencies:", - " none: Search all configured registries (default)", - " standard: Don't search on "~defaultRegistryURL, - " all: Search none of the configured registries", + "Sets a mode for skipping the search on certain package registry types:", + " none: Search all configured or default registries (default)", + " standard: Don't search the main registry (e.g. "~defaultRegistryURLs[0]~")", + " configured: Skip all default and user configured registries", + " all: Only search registries specified with --registry", ]); args.getopt("annotate", &annotate, ["Do not perform any action, just print what would be done"]); args.getopt("bare", &bare, ["Read only packages contained in the current directory"]); args.getopt("v|verbose", &verbose, ["Print diagnostic output"]); args.getopt("vverbose", &vverbose, ["Print debug output"]); args.getopt("q|quiet", &quiet, ["Only print warnings and errors"]); + args.getopt("verror", &verror, ["Only print errors"]); args.getopt("vquiet", &vquiet, ["Print no messages"]); args.getopt("cache", &placementLocation, ["Puts any fetched packages in the specified location [local|system|user]."]); } @@ -439,8 +486,9 @@ this.argumentsPattern = "[ [...]]"; this.description = "Initializes an empty package skeleton"; this.helpText = [ - "Initializes an empty package of the specified type in the given directory. By default, the current working dirctory is used." + "Initializes an empty package of the specified type in the given directory. By default, the current working directory is used." ]; + this.acceptsAppArgs = true; } override void prepare(scope CommandArgs args) @@ -451,6 +499,7 @@ "minimal - simple \"hello world\" project (default)", "vibe.d - minimal HTTP server based on vibe.d", "deimos - skeleton for C header bindings", + "custom - custom project provided by dub package", ]); args.getopt("f|format", &m_format, [ "Sets the format to use for the package description file. Possible values:", @@ -462,14 +511,13 @@ override int execute(Dub dub, string[] free_args, string[] app_args) { string dir; - enforceUsage(app_args.empty, "Unexpected application arguments."); if (free_args.length) { dir = free_args[0]; free_args = free_args[1 .. $]; } - string input(string caption, string default_value) + static string input(string caption, string default_value) { writef("%s [%s]: ", caption, default_value); auto inp = readln(); @@ -477,6 +525,8 @@ } void depCallback(ref PackageRecipe p, ref PackageFormat fmt) { + import std.datetime: Clock; + if (m_nonInteractive) return; while (true) { @@ -490,39 +540,36 @@ } } auto author = p.authors.join(", "); - p.name = input("Name", p.name); + while (true) { + // Tries getting the name until a valid one is given. + import std.regex; + auto nameRegex = regex(`^[a-z0-9\-_]+$`); + string triedName = input("Name", p.name); + if (triedName.matchFirst(nameRegex).empty) { + logError("Invalid name, \""~triedName~"\", names should consist only of lowercase alphanumeric characters, - and _."); + } else { + p.name = triedName; + break; + } + } p.description = input("Description", p.description); p.authors = input("Author name", author).split(",").map!(a => a.strip).array; p.license = input("License", p.license); - p.copyright = input("Copyright string", p.copyright); + string copyrightString = .format("Copyright © %s, %-(%s, %)", Clock.currTime().year, p.authors); + p.copyright = input("Copyright string", copyrightString); while (true) { - auto depname = input("Add dependency (leave empty to skip)", null); - if (!depname.length) break; - try { - auto ver = dub.getLatestVersion(depname); - auto dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); - p.buildSettings.dependencies[depname] = dep; - logInfo("Added dependency %s %s", depname, dep.versionSpec); - } catch (Exception e) { - logError("Could not find package '%s'.", depname); - logDebug("Full error: %s", e.toString().sanitize); - } + auto depspec = input("Add dependency (leave empty to skip)", null); + if (!depspec.length) break; + addDependency(dub, p, depspec); } } - //TODO: Remove this block in next version - // Checks if argument uses current method of specifying project type. - if (free_args.length) + if (!["vibe.d", "deimos", "minimal"].canFind(m_templateType)) { - if (["vibe.d", "deimos", "minimal"].canFind(free_args[0])) - { - m_templateType = free_args[0]; - free_args = free_args[1 .. $]; - logInfo("Deprecated use of init type. Use --type=[vibe.d | deimos | minimal] in future."); - } + free_args ~= m_templateType; } - dub.createEmptyPackage(Path(dir), free_args, m_templateType, m_format, &depCallback); + dub.createEmptyPackage(NativePath(dir), free_args, m_templateType, m_format, &depCallback, app_args); logInfo("Package successfully created in %s", dir.length ? dir : "."); return 0; @@ -542,6 +589,7 @@ string m_compilerName; string m_arch; string[] m_debugVersions; + string[] m_overrideConfigs; Compiler m_compiler; BuildPlatform m_buildPlatform; BuildSettings m_buildSettings; @@ -549,6 +597,7 @@ bool m_nodeps; bool m_forceRemove = false; bool m_single; + bool m_filterVersions = false; } override void prepare(scope CommandArgs args) @@ -556,11 +605,15 @@ args.getopt("b|build", &m_buildType, [ "Specifies the type of build to perform. Note that setting the DFLAGS environment variable will override the build type with custom flags.", "Possible names:", - " debug (default), plain, release, release-debug, release-nobounds, unittest, profile, profile-gc, docs, ddox, cov, unittest-cov and custom types" + " debug (default), plain, release, release-debug, release-nobounds, unittest, profile, profile-gc, docs, ddox, cov, unittest-cov, syntax and custom types" ]); args.getopt("c|config", &m_buildConfig, [ "Builds the specified configuration. Configurations can be defined in dub.json" ]); + args.getopt("override-config", &m_overrideConfigs, [ + "Uses the specified configuration for a certain dependency. Can be specified multiple times.", + "Format: --override-config=/" + ]); args.getopt("compiler", &m_compilerName, [ "Specifies the compiler binary to use (can be a path).", "Arbitrary pre- and suffixes to the identifiers below are recognized (e.g. ldc2 or dmd-2.063) and matched to the proper compiler type:", @@ -573,10 +626,7 @@ "Define the specified debug version identifier when building - can be used multiple times" ]); args.getopt("nodeps", &m_nodeps, [ - "Do not check/update dependencies before building" - ]); - args.getopt("force-remove", &m_forceRemove, [ - "Force deletion of fetched packages with untracked files when upgrading" + "Do not resolve missing dependencies before building" ]); args.getopt("build-mode", &m_buildMode, [ "Specifies the way the compiler and linker are invoked. Valid values:", @@ -585,11 +635,18 @@ args.getopt("single", &m_single, [ "Treats the package name as a filename. The file must contain a package recipe comment." ]); + args.getopt("force-remove", &m_forceRemove, [ + "Deprecated option that does nothing." + ]); + args.getopt("filter-versions", &m_filterVersions, [ + "[Experimental] Filter version identifiers and debug version identifiers to improve build cache efficiency." + ]); } protected void setupPackage(Dub dub, string package_name, string default_build_type = "debug") { if (!m_compilerName.length) m_compilerName = dub.defaultCompiler; + if (!m_arch.length) m_arch = dub.defaultArchitecture; m_compiler = getCompiler(m_compilerName); m_buildPlatform = m_compiler.determinePlatform(m_buildSettings, m_compilerName, m_arch); m_buildSettings.addDebugVersions(m_debugVersions); @@ -612,8 +669,6 @@ } if (!m_nodeps) { - // TODO: only upgrade(select) if necessary, only upgrade(upgrade) every now and then - // retrieve missing packages dub.project.reinit(); if (!dub.project.hasAllDependencies) { @@ -621,14 +676,15 @@ if (m_single) dub.upgrade(UpgradeOptions.select | UpgradeOptions.noSaveSelections); else dub.upgrade(UpgradeOptions.select); } - - if (!m_single) { - logDiagnostic("Checking for upgrades."); - dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly|UpgradeOptions.useCachedResult); - } } dub.project.validate(); + + foreach (sc; m_overrideConfigs) { + auto idx = sc.indexOf('/'); + enforceUsage(idx >= 0, "Expected \"/\" as argument to --override-config."); + dub.project.overrideConfiguration(sc[0 .. idx], sc[idx+1 .. $]); + } } private bool loadSpecificPackage(Dub dub, string package_name) @@ -639,17 +695,22 @@ return true; } + bool from_cwd = package_name.length == 0 || package_name.startsWith(":"); // load package in root_path to enable searching for sub packages - if (loadCwdPackage(dub, package_name.length == 0)) { + if (loadCwdPackage(dub, from_cwd)) { if (package_name.startsWith(":")) - package_name = dub.projectName ~ package_name; - if (!package_name.length) return true; + { + auto pack = dub.packageManager.getSubPackage(dub.project.rootPackage, package_name[1 .. $], false); + dub.loadPackage(pack); + return true; + } + if (from_cwd) return true; } enforce(package_name.length, "No valid root package found - aborting."); auto pack = dub.packageManager.getFirstPackage(package_name); - enforce(pack, "Failed to find a package named '"~package_name~"'."); + enforce(pack, "Failed to find a package named '"~package_name~"' locally."); logInfo("Building package %s in %s", pack.name, pack.path.toNativeString()); dub.rootPath = pack.path; dub.loadPackage(pack); @@ -745,6 +806,7 @@ gensettings.compiler = m_compiler; gensettings.buildSettings = m_buildSettings; gensettings.combined = m_combined; + gensettings.filterVersions = m_filterVersions; gensettings.run = m_run; gensettings.runArgs = app_args; gensettings.force = m_force; @@ -760,6 +822,10 @@ } class BuildCommand : GenerateCommand { + protected { + bool m_yes; // automatic yes to prompts; + bool m_nonInteractive; + } this() { this.name = "build"; @@ -779,14 +845,87 @@ args.getopt("f|force", &m_force, [ "Forces a recompilation even if the target is up to date" ]); + args.getopt("y|yes", &m_yes, [ + `Automatic yes to prompts. Assume "yes" as answer to all interactive prompts.` + ]); + args.getopt("n|non-interactive", &m_nonInteractive, [ + "Don't enter interactive mode." + ]); super.prepare(args); m_generator = "build"; } override int execute(Dub dub, string[] free_args, string[] app_args) { + // single package files don't need to be downloaded, they are on the disk. + if (free_args.length < 1 || m_single) + return super.execute(dub, free_args, app_args); + + if (!m_nonInteractive) + { + const packageParts = splitPackageName(free_args[0]); + if (auto rc = fetchMissingPackages(dub, packageParts)) + return rc; + free_args[0] = packageParts.name; + } return super.execute(dub, free_args, app_args); } + + private int fetchMissingPackages(Dub dub, in PackageAndVersion packageParts) + { + + static bool input(string caption, bool default_value = true) { + writef("%s [%s]: ", caption, default_value ? "Y/n" : "y/N"); + auto inp = readln(); + string userInput = "y"; + if (inp.length > 1) + userInput = inp[0 .. $ - 1].toLower; + + switch (userInput) { + case "no", "n", "0": + return false; + case "yes", "y", "1": + default: + return true; + } + } + + Dependency dep; + + if (packageParts.version_.length > 0) { + // the user provided a version manually + dep = Dependency(packageParts.version_); + } else { + if (packageParts.name.startsWith(":") || + dub.packageManager.getFirstPackage(packageParts.name)) + // found locally + return 0; + + // search for the package and filter versions for exact matches + auto search = dub.searchPackages(packageParts.name) + .map!(tup => tup[1].find!(p => p.name == packageParts.name)) + .filter!(ps => !ps.empty); + if (search.empty) { + logWarn("Package '%s' was neither found locally nor online.", packageParts.name); + return 2; + } + + const p = search.front.front; + logInfo("Package '%s' was not found locally but is available online:", packageParts.name); + logInfo("---"); + logInfo("Description: %s", p.description); + logInfo("Version: %s", p.version_); + logInfo("---"); + + const answer = m_yes ? true : input("Do you want to fetch '%s' now?".format(packageParts.name)); + if (!answer) + return 0; + dep = Dependency(p.version_); + } + + dub.fetch(packageParts.name, dep, dub.defaultPlacementLocation, FetchOptions.none); + return 0; + } } class RunCommand : BuildCommand { @@ -821,6 +960,7 @@ private { string m_mainFile; bool m_combined = false; + bool m_parallel = false; bool m_force = false; } @@ -859,6 +999,9 @@ args.getopt("combined", &m_combined, [ "Tries to build the whole project in a single compiler run." ]); + args.getopt("parallel", &m_parallel, [ + "Runs multiple compiler instances in parallel, if possible." + ]); args.getopt("f|force", &m_force, [ "Forces a recompilation even if the target is up to date" ]); @@ -886,12 +1029,14 @@ settings.buildMode = m_buildMode; settings.buildSettings = m_buildSettings; settings.combined = m_combined; + settings.filterVersions = m_filterVersions; + settings.parallelBuild = m_parallel; settings.force = m_force; settings.tempBuild = m_single; settings.run = true; settings.runArgs = app_args; - dub.testProject(settings, m_buildConfig, Path(m_mainFile)); + dub.testProject(settings, m_buildConfig, NativePath(m_mainFile)); return 0; } } @@ -984,7 +1129,7 @@ // disable all log output to stdout and use "writeln" to output the JSON description auto ll = getLogLevel(); - setLogLevel(LogLevel.warn); + setLogLevel(max(ll, LogLevel.warn)); scope (exit) setLogLevel(ll); string package_name; @@ -1001,6 +1146,7 @@ settings.config = config; settings.buildType = m_buildType; settings.compiler = m_compiler; + settings.filterVersions = m_filterVersions; if (m_importPaths) { m_data = ["import-paths"]; m_dataList = true; } else if (m_stringImportPaths) { m_data = ["string-import-paths"]; m_dataList = true; } @@ -1063,28 +1209,65 @@ /******************************************************************************/ -/* FETCH / REMOVE / UPGRADE */ +/* FETCH / ADD / REMOVE / UPGRADE */ /******************************************************************************/ +class AddCommand : Command { + this() + { + this.name = "add"; + this.argumentsPattern = "[@] []"; + this.description = "Adds dependencies to the package file."; + this.helpText = [ + "Adds as dependencies.", + "", + "Running \"dub add \" is the same as adding to the \"dependencies\" section in dub.json/dub.sdl.", + "If no version is specified for one of the packages, dub will query the registry for the latest version." + ]; + } + + override void prepare(scope CommandArgs args) {} + + override int execute(Dub dub, string[] free_args, string[] app_args) + { + import dub.recipe.io : readPackageRecipe, writePackageRecipe; + import dub.internal.vibecompat.core.file : existsFile; + enforceUsage(free_args.length != 0, "Expected one or more arguments."); + enforceUsage(app_args.length == 0, "Unexpected application arguments."); + + if (!loadCwdPackage(dub, true)) return 1; + auto recipe = dub.project.rootPackage.rawRecipe.clone; + + foreach (depspec; free_args) { + if (!addDependency(dub, recipe, depspec)) + return 1; + } + writePackageRecipe(dub.project.rootPackage.recipePath, recipe); + + return 0; + } +} + class UpgradeCommand : Command { private { bool m_prerelease = false; bool m_forceRemove = false; bool m_missingOnly = false; bool m_verify = false; + bool m_dryRun = false; } this() { this.name = "upgrade"; - this.argumentsPattern = "[]"; - this.description = "Forces an upgrade of all dependencies"; + this.argumentsPattern = "[]"; + this.description = "Forces an upgrade of the dependencies"; this.helpText = [ "Upgrades all dependencies of the package by querying the package registry(ies) for new versions.", "", - "This will also update the versions stored in the selections file ("~SelectedVersions.defaultFile~") accordingly.", + "This will update the versions stored in the selections file ("~SelectedVersions.defaultFile~") accordingly.", "", - "If a package specified, (only) that package will be upgraded. Otherwise all direct and indirect dependencies of the current package will get upgraded." + "If one or more package names are specified, only those dependencies will be upgraded. Otherwise all direct and indirect dependencies of the root package will get upgraded." ]; } @@ -1093,15 +1276,18 @@ args.getopt("prerelease", &m_prerelease, [ "Uses the latest pre-release version, even if release versions are available" ]); - args.getopt("force-remove", &m_forceRemove, [ - "Force deletion of fetched packages with untracked files" - ]); args.getopt("verify", &m_verify, [ - "Updates the project and performs a build. If successful, rewrites the selected versions file ." + "Updates the project and performs a build. If successful, rewrites the selected versions file ." + ]); + args.getopt("dry-run", &m_dryRun, [ + "Only print what would be upgraded, but don't actually upgrade anything." ]); args.getopt("missing-only", &m_missingOnly, [ "Performs an upgrade only for dependencies that don't yet have a version selected. This is also done automatically before each build." ]); + args.getopt("force-remove", &m_forceRemove, [ + "Deprecated option that does nothing." + ]); } override int execute(Dub dub, string[] free_args, string[] app_args) @@ -1114,9 +1300,8 @@ auto options = UpgradeOptions.upgrade|UpgradeOptions.select; if (m_missingOnly) options &= ~UpgradeOptions.upgrade; if (m_prerelease) options |= UpgradeOptions.preRelease; - if (m_forceRemove) options |= UpgradeOptions.forceRemove; - enforceUsage(app_args.length == 0, "Upgrading a specific package is not yet implemented."); - dub.upgrade(options); + if (m_dryRun) options |= UpgradeOptions.dryRun; + dub.upgrade(options, free_args); return 0; } } @@ -1135,7 +1320,7 @@ ]); args.getopt("force-remove", &m_forceRemove, [ - "Force deletion of fetched packages with untracked files" + "Deprecated option that does nothing" ]); } @@ -1146,17 +1331,17 @@ this() { this.name = "fetch"; - this.argumentsPattern = ""; + this.argumentsPattern = "[@]"; this.description = "Manually retrieves and caches a package"; this.helpText = [ - "Note: Use the \"dependencies\" field in the package description file (e.g. dub.json) if you just want to use a certain package as a dependency, you don't have to explicitly fetch packages.", + "Note: Use \"dub add \" if you just want to use a certain package as a dependency, you don't have to explicitly fetch packages.", "", - "Explicit retrieval/removal of packages is only needed when you want to put packages to a place where several applications can share these. If you just have an dependency to a package, just add it to your dub.json, dub will do the rest for you.", + "Explicit retrieval/removal of packages is only needed when you want to put packages in a place where several applications can share them. If you just have a dependency to add, use the `add` command. Dub will do the rest for you.", "", "Without specified options, placement/removal will default to a user wide shared location.", "", "Complete applications can be retrieved and run easily by e.g.", - "$ dub fetch vibelog --local", + "$ dub fetch vibelog --cache=local", "$ cd vibelog", "$ dub", "", @@ -1182,9 +1367,11 @@ FetchOptions fetchOpts; fetchOpts |= FetchOptions.forceBranchUpgrade; - fetchOpts |= m_forceRemove ? FetchOptions.forceRemove : FetchOptions.none; if (m_version.length) dub.fetch(name, Dependency(m_version), location, fetchOpts); - else { + else if (name.canFind("@", "=")) { + const parts = name.splitPackageName; + dub.fetch(parts.name, Dependency(parts.version_), location, fetchOpts); + } else { try { dub.fetch(name, Dependency(">=0.0.0"), location, fetchOpts); logInfo( @@ -1269,9 +1456,9 @@ } if (m_nonInteractive || !m_version.empty) - dub.remove(package_id, m_version, location, m_forceRemove); + dub.remove(package_id, m_version, location); else - dub.remove(package_id, location, m_forceRemove, &resolveVersion); + dub.remove(package_id, location, &resolveVersion); return 0; } } @@ -1404,6 +1591,8 @@ override void prepare(scope CommandArgs args) {} override int execute(Dub dub, string[] free_args, string[] app_args) { + enforceUsage(free_args.length == 0, "Expecting no extra arguments."); + enforceUsage(app_args.length == 0, "The list command supports no application arguments."); logInfo("Packages present in the system and known to dub:"); foreach (p; dub.packageManager.getPackageIterator()) logInfo(" %s %s: %s", p.name, p.version_, p.path.toNativeString()); @@ -1412,16 +1601,6 @@ } } -class ListInstalledCommand : ListCommand { - this() { this.name = "list-installed"; hidden = true; } - override void prepare(scope CommandArgs args) { super.prepare(args); } - override int execute(Dub dub, string[] free_args, string[] app_args) - { - warnRenamed("list-installed", "list"); - return super.execute(dub, free_args, app_args); - } -} - class SearchCommand : Command { this() { @@ -1491,8 +1670,9 @@ auto scope_ = m_system ? LocalPackageType.system : LocalPackageType.user; auto pack = free_args[0]; auto ver = Dependency(free_args[1]); - if (existsFile(Path(free_args[2]))) { - auto target = Path(free_args[2]); + if (existsFile(NativePath(free_args[2]))) { + auto target = NativePath(free_args[2]); + if (!target.absolute) target = NativePath(getcwd()) ~ target; dub.packageManager.addOverride(scope_, pack, ver, target); logInfo("Added override %s %s => %s", pack, ver, target); } else { @@ -1621,7 +1801,7 @@ { args.getopt("compiler-status", &m_compilerStatusCode, ["The expected status code of the compiler run"]); args.getopt("compiler-regex", &m_compilerRegex, ["A regular expression used to match against the compiler output"]); - args.getopt("linker-status", &m_linkerStatusCode, ["The expected status code of the liner run"]); + args.getopt("linker-status", &m_linkerStatusCode, ["The expected status code of the linker run"]); args.getopt("linker-regex", &m_linkerRegex, ["A regular expression used to match against the linker output"]); args.getopt("program-status", &m_programStatusCode, ["The expected status code of the built executable"]); args.getopt("program-regex", &m_programRegex, ["A regular expression used to match against the program output"]); @@ -1641,7 +1821,7 @@ import std.format : formattedWrite; if (m_testPackage.length) { - dub = new Dub(Path(getcwd())); + dub = new Dub(NativePath(getcwd())); setupPackage(dub, m_testPackage); m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform); @@ -1653,6 +1833,7 @@ gensettings.compiler = m_compiler; gensettings.buildSettings = m_buildSettings; gensettings.combined = m_combined; + gensettings.filterVersions = m_filterVersions; gensettings.run = m_programStatusCode != int.min || m_programRegex.length; gensettings.runArgs = app_args; gensettings.force = true; @@ -1670,10 +1851,10 @@ } } else { enforceUsage(free_args.length == 1, "Expected destination path."); - auto path = Path(free_args[0]); + auto path = NativePath(free_args[0]); path.normalize(); - enforceUsage(path.length > 0, "Destination path must not be empty."); - if (!path.absolute) path = Path(getcwd()) ~ path; + enforceUsage(!path.empty, "Destination path must not be empty."); + if (!path.absolute) path = NativePath(getcwd()) ~ path; enforceUsage(!path.startsWith(dub.rootPath), "Destination path must not be a sub directory of the tested package!"); setupPackage(dub, null); @@ -1681,7 +1862,7 @@ if (m_buildConfig.empty) m_buildConfig = prj.getDefaultConfiguration(m_buildPlatform); - void copyFolderRec(Path folder, Path dstfolder) + void copyFolderRec(NativePath folder, NativePath dstfolder) { mkdirRecurse(dstfolder.toNativeString()); foreach (de; iterateDirectory(folder.toNativeString())) { @@ -1700,13 +1881,13 @@ } static void fixPathDependency(string pack, ref Dependency dep) { - if (dep.path.length > 0) { + if (!dep.path.empty) { auto mainpack = getBasePackageName(pack); - dep.path = Path("../") ~ mainpack; + dep.path = NativePath("../") ~ mainpack; } } - void fixPathDependencies(ref PackageRecipe recipe, Path base_path) + void fixPathDependencies(ref PackageRecipe recipe, NativePath base_path) { foreach (name, ref dep; recipe.buildSettings.dependencies) fixPathDependency(name, dep); @@ -1717,7 +1898,7 @@ foreach (ref subp; recipe.subPackages) if (subp.path.length) { - auto sub_path = base_path ~ Path(subp.path); + auto sub_path = base_path ~ NativePath(subp.path); auto pack = prj.packageManager.getOrLoadPackage(sub_path); fixPathDependencies(pack.recipe, sub_path); pack.storeInfo(sub_path); @@ -2017,3 +2198,59 @@ { logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr); } + +private bool addDependency(Dub dub, ref PackageRecipe recipe, string depspec) +{ + Dependency dep; + const parts = splitPackageName(depspec); + const depname = parts.name; + if (parts.version_) + dep = Dependency(parts.version_); + else + { + try { + const ver = dub.getLatestVersion(depname); + dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); + } catch (Exception e) { + logError("Could not find package '%s'.", depname); + logDebug("Full error: %s", e.toString().sanitize); + return false; + } + } + recipe.buildSettings.dependencies[depname] = dep; + logInfo("Adding dependency %s %s", depname, dep.versionSpec); + return true; +} + +private struct PackageAndVersion +{ + string name; + string version_; +} + +/* Split = and @ + into `name` and `version_`. */ +private PackageAndVersion splitPackageName(string packageName) +{ + + // split = + auto parts = packageName.findSplit("="); + if (parts[1].empty) { + // support splitting @ too + parts = packageName.findSplit("@"); + } + + PackageAndVersion p; + p.name = parts[0]; + if (!parts[1].empty) + p.version_ = parts[2]; + return p; +} + +// https://github.com/dlang/dub/issues/1681 +unittest +{ + auto pv = splitPackageName(""); + assert(pv.name == ""); + assert(pv.version_ is null); +} diff --git a/source/dub/compilers/buildsettings.d b/source/dub/compilers/buildsettings.d index de2626b..cfc39c4 100644 --- a/source/dub/compilers/buildsettings.d +++ b/source/dub/compilers/buildsettings.d @@ -10,10 +10,9 @@ import dub.internal.vibecompat.inet.path; import std.array : array; -import std.algorithm : filter; +import std.algorithm : filter, any; import std.path : globMatch; -static if (__VERSION__ >= 2067) - import std.typecons : BitFlags; +import std.typecons : BitFlags; /// BuildPlatform specific settings, like needed libraries or additional @@ -32,8 +31,11 @@ string[] linkerFiles; string[] sourceFiles; string[] copyFiles; + string[] extraDependencyFiles; string[] versions; string[] debugVersions; + string[] versionFilters; + string[] debugVersionFilters; string[] importPaths; string[] stringImportPaths; string[] importFiles; @@ -42,6 +44,8 @@ string[] postGenerateCommands; string[] preBuildCommands; string[] postBuildCommands; + string[] preRunCommands; + string[] postRunCommands; @byName BuildRequirements requirements; @byName BuildOptions options; @@ -68,8 +72,11 @@ addLinkerFiles(bs.linkerFiles); addSourceFiles(bs.sourceFiles); addCopyFiles(bs.copyFiles); + addExtraDependencyFiles(bs.extraDependencyFiles); addVersions(bs.versions); addDebugVersions(bs.debugVersions); + addVersionFilters(bs.versionFilters); + addDebugVersionFilters(bs.debugVersionFilters); addImportPaths(bs.importPaths); addStringImportPaths(bs.stringImportPaths); addImportFiles(bs.importFiles); @@ -78,6 +85,8 @@ addPostGenerateCommands(bs.postGenerateCommands); addPreBuildCommands(bs.preBuildCommands); addPostBuildCommands(bs.postBuildCommands); + addPreRunCommands(bs.preRunCommands); + addPostRunCommands(bs.postRunCommands); } void addDFlags(in string[] value...) { dflags ~= value; } @@ -90,18 +99,22 @@ void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); } void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } void addCopyFiles(in string[] value...) { add(copyFiles, value); } + void addExtraDependencyFiles(in string[] value...) { add(extraDependencyFiles, value); } void addVersions(in string[] value...) { add(versions, value); } void addDebugVersions(in string[] value...) { add(debugVersions, value); } + void addVersionFilters(in string[] value...) { add(versionFilters, value); } + void addDebugVersionFilters(in string[] value...) { add(debugVersionFilters, value); } void addImportPaths(in string[] value...) { add(importPaths, value); } void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); } void addImportFiles(in string[] value...) { add(importFiles, value); } - void removeImportFiles(in string[] value...) { removePaths(importFiles, value); } void addStringImportFiles(in string[] value...) { addSI(stringImportFiles, value); } void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } + void addPreRunCommands(in string[] value...) { add(preRunCommands, value, false); } + void addPostRunCommands(in string[] value...) { add(postRunCommands, value, false); } void addRequirements(in BuildRequirement[] value...) { foreach (v; value) this.requirements |= v; } void addRequirements(in BuildRequirements value) { this.requirements |= value; } void addOptions(in BuildOption[] value...) { foreach (v; value) this.options |= v; } @@ -109,79 +122,119 @@ void removeOptions(in BuildOption[] value...) { foreach (v; value) this.options &= ~v; } void removeOptions(in BuildOptions value) { this.options &= ~value; } - // Adds vals to arr without adding duplicates. - private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) +private: + static auto filterDuplicates(T)(ref string[] arr, in T vals, bool noDuplicates = true) { - if (!no_duplicates) { - arr ~= vals; - return; - } - - foreach (v; vals) { - bool found = false; - foreach (i; 0 .. arr.length) - if (arr[i] == v) { - found = true; - break; - } - if (!found) arr ~= v; - } + return noDuplicates + ? vals.filter!(filtered => !arr.any!(item => item == filtered)).array + : vals; } - private void prepend(ref string[] arr, in string[] vals, bool no_duplicates = true) + // Append vals to arr without adding duplicates. + static void add(ref string[] arr, in string[] vals, bool noDuplicates = true) { - if (!no_duplicates) { - arr = vals ~ arr; - return; - } + arr ~= filterDuplicates(arr, vals, noDuplicates); + } - foreach_reverse (v; vals) { - bool found = false; - foreach (i; 0 .. arr.length) - if (arr[i] == v) { - found = true; - break; - } - if (!found) arr = v ~ arr; - } + unittest + { + auto ary = ["-dip1000", "-vgc"]; + BuildSettings.add(ary, ["-dip1000", "-vgc"]); + assert(ary == ["-dip1000", "-vgc"]); + BuildSettings.add(ary, ["-dip1001", "-vgc"], false); + assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc"]); + } + + // Prepend arr by vals without adding duplicates. + static void prepend(ref string[] arr, in string[] vals, bool noDuplicates = true) + { + arr = filterDuplicates(arr, vals, noDuplicates) ~ arr; + } + + unittest + { + auto ary = ["-dip1000", "-vgc"]; + BuildSettings.prepend(ary, ["-dip1000", "-vgc"]); + assert(ary == ["-dip1000", "-vgc"]); + BuildSettings.prepend(ary, ["-dip1001", "-vgc"], false); + assert(ary == ["-dip1001", "-vgc", "-dip1000", "-vgc"]); } // add string import files (avoids file name duplicates in addition to path duplicates) - private void addSI(ref string[] arr, in string[] vals) + static void addSI(ref string[] arr, in string[] vals) { - outer: + bool[string] existing; + foreach (v; arr) existing[NativePath(v).head.toString()] = true; foreach (v; vals) { - auto vh = Path(v).head; - foreach (ve; arr) { - if (Path(ve).head == vh) - continue outer; + auto s = NativePath(v).head.toString(); + if (s !in existing) { + existing[s] = true; + arr ~= v; } - arr ~= v; } } - private void removePaths(ref string[] arr, in string[] vals) + unittest { - bool matches(string s) - { - foreach (p; vals) - if (Path(s) == Path(p) || globMatch(s, p)) - return true; - return false; - } - arr = arr.filter!(s => !matches(s))().array(); + auto ary = ["path/foo.txt"]; + BuildSettings.addSI(ary, ["path2/foo2.txt"]); + assert(ary == ["path/foo.txt", "path2/foo2.txt"]); + BuildSettings.addSI(ary, ["path2/foo.txt"]); // no duplicate basenames + assert(ary == ["path/foo.txt", "path2/foo2.txt"]); } - private void remove(ref string[] arr, in string[] vals) + static bool pathMatch(string path, string pattern) + { + import std.functional : memoize; + + alias nativePath = memoize!((string stringPath) => NativePath(stringPath)); + + return nativePath(path) == nativePath(pattern) || globMatch(path, pattern); + } + + static void removeValuesFromArray(alias Match)(ref string[] arr, in string[] vals) { bool matches(string s) { - foreach (p; vals) - if (s == p) - return true; - return false; + return vals.any!(item => Match(s, item)); } - arr = arr.filter!(s => !matches(s))().array(); + arr = arr.filter!(s => !matches(s)).array; + } + + static void removePaths(ref string[] arr, in string[] vals) + { + removeValuesFromArray!(pathMatch)(arr, vals); + } + + unittest + { + auto ary = ["path1", "root/path1", "root/path2", "root2/path1"]; + BuildSettings.removePaths(ary, ["path1"]); + assert(ary == ["root/path1", "root/path2", "root2/path1"]); + BuildSettings.removePaths(ary, ["*/path1"]); + assert(ary == ["root/path2"]); + BuildSettings.removePaths(ary, ["foo", "bar", "root/path2"]); + assert(ary == []); + } + + static void remove(ref string[] arr, in string[] vals) + { + removeValuesFromArray!((a, b) => a == b)(arr, vals); + } + + unittest + { + import std.string : join; + + auto ary = ["path1", "root/path1", "root/path2", "root2/path1"]; + BuildSettings.remove(ary, ["path1"]); + assert(ary == ["root/path1", "root/path2", "root2/path1"]); + BuildSettings.remove(ary, ["root/path*"]); + assert(ary == ["root/path1", "root/path2", "root2/path1"]); + BuildSettings.removePaths(ary, ["foo", "root/path2", "bar", "root2/path1"]); + assert(ary == ["root/path1"]); + BuildSettings.remove(ary, ["root/path1", "foo"]); + assert(ary == []); } } @@ -231,26 +284,9 @@ struct BuildRequirements { import dub.internal.vibecompat.data.serialization : ignore; - static if (__VERSION__ >= 2067) { - @ignore BitFlags!BuildRequirement values; - this(BuildRequirement req) { values = req; } - } else { - @ignore BuildRequirement values; - this(BuildRequirement req) { values = req; } - BuildRequirement[] toRepresentation() - const { - BuildRequirement[] ret; - for (int f = 1; f <= BuildRequirement.max; f *= 2) - if (values & f) ret ~= cast(BuildRequirement)f; - return ret; - } - static BuildRequirements fromRepresentation(BuildRequirement[] v) - { - BuildRequirements ret; - foreach (f; v) ret.values |= f; - return ret; - } - } + @ignore BitFlags!BuildRequirement values; + this(BuildRequirement req) { values = req; } + alias values this; } @@ -278,35 +314,20 @@ deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) profileGC = 1<<21, /// Profile runtime allocations + pic = 1<<22, /// Generate position independent code + betterC = 1<<23, /// Compile in betterC mode (-betterC) + // for internal usage - _docs = 1<<22, // Write ddoc to docs - _ddox = 1<<23, // Compile docs.json + _docs = 1<<24, // Write ddoc to docs + _ddox = 1<<25 // Compile docs.json } struct BuildOptions { import dub.internal.vibecompat.data.serialization : ignore; - static if (__VERSION__ >= 2067) { - @ignore BitFlags!BuildOption values; - this(BuildOption opt) { values = opt; } - this(BitFlags!BuildOption v) { values = v; } - } else { - @ignore BuildOption values; - this(BuildOption opt) { values = opt; } - BuildOption[] toRepresentation() - const { - BuildOption[] ret; - for (int f = 1; f <= BuildOption.max; f *= 2) - if (values & f) ret ~= cast(BuildOption)f; - return ret; - } - static BuildOptions fromRepresentation(BuildOption[] v) - { - BuildOptions ret; - foreach (f; v) ret.values |= f; - return ret; - } - } + @ignore BitFlags!BuildOption values; + this(BuildOption opt) { values = opt; } + this(BitFlags!BuildOption v) { values = v; } alias values this; } @@ -327,4 +348,5 @@ | BuildOption.noBoundsCheck | BuildOption.profile | BuildOption.ignoreUnknownPragmas | BuildOption.syntaxOnly | BuildOption.warnings | BuildOption.warningsAsErrors | BuildOption.ignoreDeprecations | BuildOption.deprecationWarnings - | BuildOption.deprecationErrors | BuildOption.property | BuildOption.profileGC; + | BuildOption.deprecationErrors | BuildOption.property | BuildOption.profileGC + | BuildOption.pic; diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index ca4f7ac..42e6cf9 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -8,18 +8,21 @@ module dub.compilers.compiler; public import dub.compilers.buildsettings; +public import dub.dependency : Dependency; public import dub.platform : BuildPlatform, matchesSpecification; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; import std.conv; import std.exception; import std.process; +import std.typecons : Flag; /** Returns a compiler handler for a given binary name. @@ -88,7 +91,10 @@ /// Convert linker flags to compiler format string[] lflagsToDFlags(in string[] lflags) const; - + + /// Determines compiler version + string determineVersion(string compiler_binary, string verboseOutput); + /** Runs a tool and provides common boilerplate code. This method should be used by `Compiler` implementations to invoke the @@ -115,11 +121,17 @@ enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); } - /// Compiles platform probe file with the specified compiler and parses its output. - protected final BuildPlatform probePlatform(string compiler_binary, string[] args, string arch_override) + /** Compiles platform probe file with the specified compiler and parses its output. + Params: + compiler_binary = binary to invoke compiler with + args = arguments for the probe compilation + arch_override = special handler for x86_mscoff + */ + protected final BuildPlatform probePlatform(string compiler_binary, string[] args, + string arch_override) { - import std.string : format; - import dub.compilers.utils : generatePlatformProbeFile, readPlatformProbe; + import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe; + import std.string : format, strip; auto fil = generatePlatformProbeFile(); @@ -127,14 +139,30 @@ enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s", compiler_binary, result.output)); - auto build_platform = readPlatformProbe(result.output); + auto build_platform = readPlatformJsonProbe(result.output); build_platform.compilerBinary = compiler_binary; if (build_platform.compiler != this.name) { - logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". This will probably result in build errors.`, - build_platform.compiler, this.name); + logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". `~ + `This will probably result in build errors.`, build_platform.compiler, this.name); } + auto ver = determineVersion(compiler_binary, result.output) + .strip; + if (ver.empty) { + logWarn(`Could not probe the compiler version for "%s". ` ~ + `Toolchain requirements might be ineffective`, build_platform.compiler); + } + else { + build_platform.compilerVersion = ver; + } + + // Hack: see #1059 + // When compiling with --arch=x86_mscoff build_platform.architecture is equal to ["x86"] and canFind below is false. + // This hack prevents unnesessary warning 'Failed to apply the selected architecture x86_mscoff. Got ["x86"]'. + // And also makes "x86_mscoff" available as a platform specifier in the package recipe + if (arch_override == "x86_mscoff") + build_platform.architecture ~= arch_override; if (arch_override.length && !build_platform.architecture.canFind(arch_override)) { logWarn(`Failed to apply the selected architecture %s. Got %s.`, arch_override, build_platform.architecture); diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index 6182ff3..bf06e4c 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -12,7 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; -import dub.platform; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -20,9 +20,26 @@ import std.exception; import std.file; import std.process; -import std.random; import std.typecons; +// Determines whether the specified process is running under WOW64 or an Intel64 of x64 processor. +version (Windows) +private Nullable!bool isWow64() { + // See also: https://docs.microsoft.com/de-de/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo + import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO, PROCESSOR_ARCHITECTURE_AMD64; + + static Nullable!bool result; + + // A process's architecture won't change over while the process is in memory + // Return the cached result + if (!result.isNull) + return result; + + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + result = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; + return result; +} class DMDCompiler : Compiler { private static immutable s_options = [ @@ -30,7 +47,7 @@ tuple(BuildOption.releaseMode, ["-release"]), tuple(BuildOption.coverage, ["-cov"]), tuple(BuildOption.debugInfo, ["-g"]), - tuple(BuildOption.debugInfoC, ["-gc"]), + tuple(BuildOption.debugInfoC, ["-g"]), tuple(BuildOption.alwaysStackFrame, ["-gs"]), tuple(BuildOption.stackStomping, ["-gx"]), tuple(BuildOption.inline, ["-inline"]), @@ -48,6 +65,7 @@ tuple(BuildOption.deprecationErrors, ["-de"]), tuple(BuildOption.property, ["-property"]), tuple(BuildOption.profileGC, ["-profile=gc"]), + tuple(BuildOption.betterC, ["-betterC"]), tuple(BuildOption._docs, ["-Dddocs"]), tuple(BuildOption._ddox, ["-Xfdocs.json", "-Df__dummy.html"]), @@ -55,18 +73,63 @@ @property string name() const { return "dmd"; } + enum dmdVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary dmd +version v2.082.0 +config /etc/dmd.conf +`; + auto re = regex(dmdVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "2.082.0"); + } + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary dmd +version v2.084.0-beta.1 +config /etc/dmd.conf +`; + auto re = regex(dmdVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "2.084.0-beta.1"); + } + + string determineVersion(string compiler_binary, string verboseOutput) + { + import std.regex : matchFirst, regex; + auto ver = matchFirst(verboseOutput, regex(dmdVersionRe, "m")); + return ver && ver.length > 1 ? ver[1] : null; + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; switch (arch_override) { default: throw new Exception("Unsupported architecture: "~arch_override); - case "": break; + case "": + // Don't use Optlink by default on Windows + version (Windows) { + const is64bit = isWow64(); + if (!is64bit.isNull) + arch_flags = [is64bit.get ? "-m64" : "-m32mscoff"]; + } + break; case "x86": arch_flags = ["-m32"]; break; case "x86_64": arch_flags = ["-m64"]; break; + case "x86_mscoff": arch_flags = ["-m32mscoff"]; break; } settings.addDFlags(arch_flags); - return probePlatform(compiler_binary, arch_flags ~ ["-quiet", "-c", "-o-"], arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-quiet", "-c", "-o-", "-v"], + arch_override + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -116,7 +179,7 @@ } version (Posix) { - if (settings.targetType == TargetType.dynamicLibrary) + if (settings.options & BuildOption.pic) settings.addDFlags("-fPIC"); } @@ -142,9 +205,13 @@ string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const { + import std.conv: text; assert(settings.targetName.length > 0, "No target name set."); final switch (settings.targetType) { - case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); + case TargetType.autodetect: + assert(false, + text("Configurations must have a concrete target type, ", settings.targetName, + " has ", settings.targetType)); case TargetType.none: return null; case TargetType.sourceLibrary: return null; case TargetType.executable: @@ -159,6 +226,8 @@ case TargetType.dynamicLibrary: if (platform.platform.canFind("windows")) return settings.targetName ~ ".dll"; + else if (platform.platform.canFind("osx")) + return "lib" ~ settings.targetName ~ ".dylib"; else return "lib" ~ settings.targetName ~ ".so"; case TargetType.object: if (platform.platform.canFind("windows")) @@ -189,7 +258,7 @@ } if (tpath is null) - tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); settings.addDFlags("-of"~tpath); } @@ -207,11 +276,11 @@ void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { import std.string; - auto tpath = Path(settings.targetPath) ~ getTargetFileName(settings, platform); + auto tpath = NativePath(settings.targetPath) ~ getTargetFileName(settings, platform); auto args = ["-of"~tpath.toNativeString()]; args ~= objects; args ~= settings.sourceFiles; - version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being speficied in the wrong order by DMD + version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD args ~= lflagsToDFlags(settings.lflags); args ~= settings.dflags.filter!(f => isLinkerDFlag(f)).array; @@ -224,7 +293,7 @@ string[] lflagsToDFlags(in string[] lflags) const { - return lflags.map!(f => "-L"~f)().array(); + return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array(); } private auto escapeArgs(in string[] args) diff --git a/source/dub/compilers/gdc.d b/source/dub/compilers/gdc.d index bb5def1..4a9c0d2 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -12,7 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; -import dub.platform; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -20,7 +20,6 @@ import std.exception; import std.file; import std.process; -import std.random; import std.typecons; @@ -55,6 +54,17 @@ @property string name() const { return "gdc"; } + string determineVersion(string compiler_binary, string verboseOutput) + { + const result = execute([ + compiler_binary, + "-dumpfullversion", + "-dumpversion" + ]); + + return result.status == 0 ? result.output : null; + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; @@ -68,10 +78,11 @@ } settings.addDFlags(arch_flags); - auto binary_file = getTempFile("dub_platform_probe"); - return probePlatform(compiler_binary, - arch_flags ~ ["-c", "-o", binary_file.toNativeString()], - arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-S", "-v"], + arch_override + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -119,7 +130,7 @@ settings.lflags = null; } - if (settings.targetType == TargetType.dynamicLibrary) + if (settings.options & BuildOption.pic) settings.addDFlags("-fPIC"); assert(fields & BuildSetting.dflags); @@ -159,6 +170,8 @@ case TargetType.dynamicLibrary: if (platform.platform.canFind("windows")) return settings.targetName ~ ".dll"; + else if (platform.platform.canFind("osx")) + return "lib" ~ settings.targetName ~ ".dylib"; else return "lib" ~ settings.targetName ~ ".so"; case TargetType.object: if (platform.platform.canFind("windows")) @@ -185,7 +198,7 @@ } if (tpath is null) - tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); settings.addDFlags("-o", tpath); } @@ -209,7 +222,7 @@ args = [ "ar", "rcs", tpath ] ~ objects; } else { args = platform.compilerBinary ~ objects ~ settings.sourceFiles ~ settings.lflags ~ settings.dflags.filter!(f => isLinkageFlag(f)).array; - version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being speficied in the wrong order by DMD + version(linux) args ~= "-L--no-as-needed"; // avoids linker errors due to libraries being specified in the wrong order by DMD } logDiagnostic("%s", args.join(" ")); invokeTool(args, output_callback); @@ -220,6 +233,9 @@ string[] dflags; foreach( f; lflags ) { + if ( f == "") { + continue; + } dflags ~= "-Xlinker"; dflags ~= f; } diff --git a/source/dub/compilers/ldc.d b/source/dub/compilers/ldc.d index 8953e5e..11c2872 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -12,7 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; -import dub.platform; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -20,7 +20,6 @@ import std.exception; import std.file; import std.process; -import std.random; import std.typecons; @@ -48,6 +47,7 @@ tuple(BuildOption.deprecationErrors, ["-de"]), tuple(BuildOption.property, ["-property"]), //tuple(BuildOption.profileGC, ["-?"]), + tuple(BuildOption.betterC, ["-betterC"]), tuple(BuildOption._docs, ["-Dd=docs"]), tuple(BuildOption._ddox, ["-Xf=docs.json", "-Dd=__dummy_docs"]), @@ -55,6 +55,27 @@ @property string name() const { return "ldc"; } + enum ldcVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary /usr/bin/ldc2 +version 1.11.0 (DMD v2.081.2, LLVM 6.0.1) +config /etc/ldc2.conf (x86_64-pc-linux-gnu) +`; + auto re = regex(ldcVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "1.11.0"); + } + + string determineVersion(string compiler_binary, string verboseOutput) + { + import std.regex : matchFirst, regex; + auto ver = matchFirst(verboseOutput, regex(ldcVersionRe, "m")); + return ver && ver.length > 1 ? ver[1] : null; + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; @@ -66,7 +87,11 @@ } settings.addDFlags(arch_flags); - return probePlatform(compiler_binary, arch_flags ~ ["-c", "-o-"], arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-c", "-o-", "-v"], + arch_override + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -117,7 +142,7 @@ settings.lflags = null; } - if (settings.targetType == TargetType.dynamicLibrary) + if (settings.options & BuildOption.pic) settings.addDFlags("-relocation-model=pic"); assert(fields & BuildSetting.dflags); @@ -142,18 +167,8 @@ string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) const { - import std.string : splitLines, strip; - import std.uni : toLower; - assert(settings.targetName.length > 0, "No target name set."); - auto result = executeShell(escapeShellCommand([platform.compilerBinary, "-version"])); - enforce (result.status == 0, "Failed to determine linker used by LDC. \"" - ~platform.compilerBinary~" -version\" failed with exit code " - ~result.status.to!string()~"."); - - bool generates_coff = result.output.splitLines.find!(l => l.strip.toLower.startsWith("default target:")).front.canFind("-windows-msvc"); - final switch (settings.targetType) { case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); case TargetType.none: return null; @@ -164,11 +179,13 @@ else return settings.targetName; case TargetType.library: case TargetType.staticLibrary: - if (generates_coff) return settings.targetName ~ ".lib"; + if (generatesCOFF(platform)) return settings.targetName ~ ".lib"; else return "lib" ~ settings.targetName ~ ".a"; case TargetType.dynamicLibrary: if (platform.platform.canFind("windows")) return settings.targetName ~ ".dll"; + else if (platform.platform.canFind("osx")) + return "lib" ~ settings.targetName ~ ".dylib"; else return "lib" ~ settings.targetName ~ ".so"; case TargetType.object: if (platform.platform.canFind("windows")) @@ -197,7 +214,7 @@ } if (tpath is null) - tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + tpath = (NativePath(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); settings.addDFlags("-of"~tpath); } @@ -219,11 +236,36 @@ string[] lflagsToDFlags(in string[] lflags) const { - return lflags.map!(s => "-L="~s)().array(); + return map!(f => "-L"~f)(lflags.filter!(f => f != "")()).array(); } private auto escapeArgs(in string[] args) { return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); } + + private static bool generatesCOFF(in BuildPlatform platform) + { + import std.string : splitLines, strip; + import std.uni : toLower; + + static bool[string] compiler_coff_map; + + if (auto pret = platform.compilerBinary in compiler_coff_map) + return *pret; + + auto result = executeShell(escapeShellCommand([platform.compilerBinary, "-version"])); + enforce (result.status == 0, "Failed to determine linker used by LDC. \"" + ~platform.compilerBinary~" -version\" failed with exit code " + ~result.status.to!string()~"."); + + bool ret = result.output + .splitLines + .find!(l => l.strip.toLower.startsWith("default target:")) + .front + .canFind("msvc"); + + compiler_coff_map[platform.compilerBinary] = ret; + return ret; + } } diff --git a/source/dub/compilers/utils.d b/source/dub/compilers/utils.d index 90cdbfb..03b3c33 100644 --- a/source/dub/compilers/utils.d +++ b/source/dub/compilers/utils.d @@ -8,7 +8,7 @@ module dub.compilers.utils; import dub.compilers.buildsettings; -import dub.platform; +import dub.platform : BuildPlatform, archCheck, compilerCheck, platformCheck; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; import std.algorithm : canFind, endsWith, filter; @@ -83,6 +83,7 @@ void resolveLibs(ref BuildSettings settings) { import std.string : format; + import std.array : array; if (settings.libs.length == 0) return; @@ -141,7 +142,7 @@ /** Searches the given list of compiler flags for ones that have a generic equivalent. - Certain compiler flags should, instead of using compiler-specfic syntax, + Certain compiler flags should, instead of using compiler-specific syntax, be specified as build options (`BuildOptions`) or built requirements (`BuildRequirements`). This function will output warning messages to assist the user in making the best choice. @@ -174,7 +175,7 @@ {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, - {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`} + {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`} ]; struct SpecialOption { @@ -191,6 +192,7 @@ {[BuildOption.optimize], "Call DUB with --build=release"}, {[BuildOption.profile], "Call DUB with --build=profile"}, {[BuildOption.unittests], "Call DUB with --build=unittest"}, + {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"}, {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, {[BuildOption.property], "This flag is deprecated and has no effect"} @@ -235,132 +237,114 @@ if (got_preamble) logWarn(""); } +/** + Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1). + The function accepts a dependency operator prefix and some text postfix. + Prefix and postfix are returned verbatim. + Params: + ver = version string, possibly with a dependency operator prefix and some + test postfix. + Returns: + A Semver compliant string +*/ +package(dub) string dmdLikeVersionToSemverLike(string ver) +{ + import std.algorithm : countUntil, joiner, map, skipOver, splitter; + import std.array : join, split; + import std.ascii : isDigit; + import std.conv : text; + import std.exception : enforce; + import std.functional : not; + import std.range : padRight; + + const start = ver.countUntil!isDigit; + enforce(start != -1, "Invalid semver: "~ver); + const prefix = ver[0 .. start]; + ver = ver[start .. $]; + + const end = ver.countUntil!(c => !c.isDigit && c != '.'); + const postfix = end == -1 ? null : ver[end .. $]; + auto verStr = ver[0 .. $-postfix.length]; + + auto comps = verStr + .splitter(".") + .map!((a) { if (a.length > 1) a.skipOver("0"); return a;}) + .padRight("0", 3); + + return text(prefix, comps.joiner("."), postfix); +} + +/// +unittest { + assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1"); + assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0"); + assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0"); + assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0"); + assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1"); + assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6"); + assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12"); +} + +private enum probeBeginMark = "__dub_probe_begin__"; +private enum probeEndMark = "__dub_probe_end__"; /** - Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) + Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) See_Also: `readPlatformProbe` */ -Path generatePlatformProbeFile() +NativePath generatePlatformProbeFile() { import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.data.json; import dub.internal.utils; - auto path = getTempFile("dub_platform_probe", ".d"); - - auto fil = openFile(path, FileMode.createTrunc); - scope (failure) { - fil.close(); - } - - // NOTE: This must be kept in sync with the dub.platform module - fil.write(q{ + // try to not use phobos in the probe to avoid long import times + enum probe = q{ module dub_platform_probe; template toString(int v) { enum toString = v.stringof; } + string stringArray(string[] ary) { + string res; + foreach (i, e; ary) { + if (i) + res ~= ", "; + res ~= '"' ~ e ~ '"'; + } + return res; + } + pragma(msg, } ~ `"` ~ probeBeginMark ~ `"` ~q{ ); pragma(msg, `{`); pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); pragma(msg, ` "platform": [`); - pragma(msg, ` ` ~ determinePlatform()); + pragma(msg, ` ` ~ determinePlatform().stringArray); pragma(msg, ` ],`); pragma(msg, ` "architecture": [`); - pragma(msg, ` ` ~ determineArchitecture()); + pragma(msg, ` ` ~ determineArchitecture().stringArray); pragma(msg, ` ],`); pragma(msg, `}`); + pragma(msg, } ~ `"` ~ probeEndMark ~ `"` ~ q{ ); - string determinePlatform() - { - string ret; - version(Windows) ret ~= `"windows", `; - version(linux) ret ~= `"linux", `; - version(Posix) ret ~= `"posix", `; - version(OSX) ret ~= `"osx", `; - version(FreeBSD) ret ~= `"freebsd", `; - version(OpenBSD) ret ~= `"openbsd", `; - version(NetBSD) ret ~= `"netbsd", `; - version(DragonFlyBSD) ret ~= `"dragonflybsd", `; - version(BSD) ret ~= `"bsd", `; - version(Solaris) ret ~= `"solaris", `; - version(AIX) ret ~= `"aix", `; - version(Haiku) ret ~= `"haiku", `; - version(SkyOS) ret ~= `"skyos", `; - version(SysV3) ret ~= `"sysv3", `; - version(SysV4) ret ~= `"sysv4", `; - version(Hurd) ret ~= `"hurd", `; - version(Android) ret ~= `"android", `; - version(Cygwin) ret ~= `"cygwin", `; - version(MinGW) ret ~= `"mingw", `; - return ret; - } + string[] determinePlatform() } ~ '{' ~ platformCheck ~ '}' ~ q{ + string[] determineArchitecture() } ~ '{' ~ archCheck ~ '}' ~ q{ + string determineCompiler() } ~ '{' ~ compilerCheck ~ '}'; - string determineArchitecture() - { - string ret; - version(X86) ret ~= `"x86", `; - version(X86_64) ret ~= `"x86_64", `; - version(ARM) ret ~= `"arm", `; - version(ARM_Thumb) ret ~= `"arm_thumb", `; - version(ARM_SoftFloat) ret ~= `"arm_softfloat", `; - version(ARM_HardFloat) ret ~= `"arm_hardfloat", `; - version(ARM64) ret ~= `"arm64", `; - version(PPC) ret ~= `"ppc", `; - version(PPC_SoftFP) ret ~= `"ppc_softfp", `; - version(PPC_HardFP) ret ~= `"ppc_hardfp", `; - version(PPC64) ret ~= `"ppc64", `; - version(IA64) ret ~= `"ia64", `; - version(MIPS) ret ~= `"mips", `; - version(MIPS32) ret ~= `"mips32", `; - version(MIPS64) ret ~= `"mips64", `; - version(MIPS_O32) ret ~= `"mips_o32", `; - version(MIPS_N32) ret ~= `"mips_n32", `; - version(MIPS_O64) ret ~= `"mips_o64", `; - version(MIPS_N64) ret ~= `"mips_n64", `; - version(MIPS_EABI) ret ~= `"mips_eabi", `; - version(MIPS_NoFloat) ret ~= `"mips_nofloat", `; - version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `; - version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `; - version(SPARC) ret ~= `"sparc", `; - version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `; - version(SPARC_SoftFP) ret ~= `"sparc_softfp", `; - version(SPARC_HardFP) ret ~= `"sparc_hardfp", `; - version(SPARC64) ret ~= `"sparc64", `; - version(S390) ret ~= `"s390", `; - version(S390X) ret ~= `"s390x", `; - version(HPPA) ret ~= `"hppa", `; - version(HPPA64) ret ~= `"hppa64", `; - version(SH) ret ~= `"sh", `; - version(SH64) ret ~= `"sh64", `; - version(Alpha) ret ~= `"alpha", `; - version(Alpha_SoftFP) ret ~= `"alpha_softfp", `; - version(Alpha_HardFP) ret ~= `"alpha_hardfp", `; - return ret; - } - - string determineCompiler() - { - version(DigitalMars) return "dmd"; - else version(GNU) return "gdc"; - else version(LDC) return "ldc"; - else version(SDC) return "sdc"; - else return null; - } - }); - - fil.close(); + auto path = getTempFile("dub_platform_probe", ".d"); + auto fil = openFile(path, FileMode.createTrunc); + fil.write(probe); return path; } /** - Processes the output generated by compiling the platform probe file. + Processes the JSON output generated by compiling the platform probe file. See_Also: `generatePlatformProbeFile`. */ -BuildPlatform readPlatformProbe(string output) +BuildPlatform readPlatformJsonProbe(string output) { import std.algorithm : map; import std.array : array; @@ -368,11 +352,11 @@ import std.string; // work around possible additional output of the compiler - auto idx1 = output.indexOf("{"); - auto idx2 = output.lastIndexOf("}"); + auto idx1 = output.indexOf(probeBeginMark); + auto idx2 = output.lastIndexOf(probeEndMark); enforce(idx1 >= 0 && idx1 < idx2, "Unexpected platform information output - does not contain a JSON object."); - output = output[idx1 .. idx2+1]; + output = output[idx1+probeBeginMark.length .. idx2]; import dub.internal.vibecompat.data.json; auto json = parseJsonString(output); diff --git a/source/dub/dependency.d b/source/dub/dependency.d index e2330fa..da1b552 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -44,6 +44,8 @@ package name is notably not part of the dependency specification. */ struct Dependency { +@safe: + private { // Shortcut to create >=0.0.0 enum ANY_IDENT = "*"; @@ -51,7 +53,7 @@ Version m_versA; bool m_inclusiveB = true; // B comparison < (true) or <= (false) Version m_versB; - Path m_path; + NativePath m_path; bool m_optional = false; bool m_default = false; } @@ -85,16 +87,16 @@ /** Constructs a new dependency specification that matches a specific path. */ - this(Path path) + this(NativePath path) { this(ANY_IDENT); m_path = path; } /// If set, overrides any version based dependency selection. - @property void path(Path value) { m_path = value; } + @property void path(NativePath value) { m_path = value; } /// ditto - @property Path path() const { return m_path; } + @property NativePath path() const { return m_path; } /// Determines if the dependency is required or optional. @property bool optional() const { return m_optional; } @@ -136,6 +138,8 @@ */ @property void versionSpec(string ves) { + static import std.string; + enforce(ves.length > 0); string orig = ves; @@ -151,7 +155,7 @@ m_inclusiveB = false; ves = ves[2..$]; m_versA = Version(expandVersion(ves)); - m_versB = Version(bumpVersion(ves)); + m_versB = Version(bumpVersion(ves) ~ "-0"); } else if (ves[0] == Version.branchPrefix) { m_inclusiveA = true; m_inclusiveB = true; @@ -198,6 +202,8 @@ /// ditto @property string versionSpec() const { + static import std.string; + string r; if (this == invalid) return "invalid"; @@ -220,7 +226,7 @@ foreach (i; 0 .. 3) { auto vp = parts[0 .. i+1].join("."); auto ve = Version(expandVersion(vp)); - auto veb = Version(expandVersion(bumpVersion(vp))); + auto veb = Version(bumpVersion(vp) ~ "-0"); if (ve == m_versA && veb == m_versB) return "~>" ~ vp; } } @@ -237,8 +243,8 @@ based. Otherwise, the given `path` will be prefixed to the existing path. */ - Dependency mapToPath(Path path) - const { + Dependency mapToPath(NativePath path) + const @trusted { // NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer if (m_path.empty || m_path.absolute) return this; else { Dependency ret = this; @@ -257,7 +263,12 @@ if (default_) ret ~= " (optional, default)"; else ret ~= " (optional)"; } - if (!path.empty) ret ~= " @"~path.toNativeString(); + + // NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer + () @trusted { + if (!path.empty) ret ~= " @"~path.toNativeString(); + } (); + return ret; } @@ -268,7 +279,8 @@ represented as a JSON object with optional "version", "path", "optional" and "default" fields. */ - Json toJson() const { + Json toJson() + const @trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer Json json; if( path.empty && !optional ){ json = Json(this.versionSpec); @@ -282,7 +294,7 @@ return json; } - unittest { + @trusted unittest { Dependency d = Dependency("==1.0.0"); assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString()); d = fromJson((fromJson(d.toJson())).toJson()); @@ -294,7 +306,8 @@ See `toJson` for a description of the JSON format. */ - static Dependency fromJson(Json verspec) { + static Dependency fromJson(Json verspec) + @trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer Dependency dep; if( verspec.type == Json.Type.object ){ if( auto pp = "path" in verspec ) { @@ -302,11 +315,11 @@ logDiagnostic("Ignoring version specification (%s) for path based dependency %s", pv.get!string, pp.get!string); dep = Dependency.any; - dep.path = Path(verspec["path"].get!string); + dep.path = NativePath(verspec["path"].get!string); } else { enforce("version" in verspec, "No version field specified!"); auto ver = verspec["version"].get!string; - // Using the string to be able to specifiy a range of versions. + // Using the string to be able to specify a range of versions. dep = Dependency(ver); } @@ -319,7 +332,7 @@ return dep; } - unittest { + @trusted unittest { assert(fromJson(parseJsonString("\">=1.0.0 <2.0.0\"")) == Dependency(">=1.0.0 <2.0.0")); Dependency parsed = fromJson(parseJsonString(` { @@ -332,7 +345,7 @@ Dependency d = Dependency.any; // supposed to ignore the version spec d.optional = true; d.default_ = true; - d.path = Path("path/to/package"); + d.path = NativePath("path/to/package"); assert(d == parsed); // optional and path not checked by opEquals. assert(d.optional == parsed.optional); @@ -365,11 +378,17 @@ } /// ditto - hash_t toHash() const nothrow @trusted { + hash_t toHash() + const nothrow @trusted { try { - auto strhash = &typeid(string).getHash; - auto str = this.toString(); - return strhash(&str); + size_t hash = 0; + hash = m_inclusiveA.hashOf(hash); + hash = m_versA.toString().hashOf(hash); + hash = m_inclusiveB.hashOf(hash); + hash = m_versB.toString().hashOf(hash); + hash = m_optional.hashOf(hash); + hash = m_default.hashOf(hash); + return hash; } catch (Exception) assert(false); } @@ -385,11 +404,18 @@ This is true in particular for the `any` constant. */ - bool matchesAny() const { - auto cmp = Dependency("*"); - cmp.optional = m_optional; - cmp.default_ = m_default; - return cmp == this; + bool matchesAny() + const { + return m_inclusiveA && m_inclusiveB + && m_versA.toString() == "0.0.0" + && m_versB == Version.maxRelease; + } + + unittest { + assert(Dependency("*").matchesAny); + assert(!Dependency(">0.0.0").matchesAny); + assert(!Dependency(">=1.0.0").matchesAny); + assert(!Dependency("<1.0.0").matchesAny); } /** Tests if the specification matches a specific version. @@ -425,20 +451,20 @@ const { if (this.matchesAny) return o; if (o.matchesAny) return this; - if (!this.valid || !o.valid) return invalid; if (m_versA.isBranch != o.m_versA.isBranch) return invalid; if (m_versB.isBranch != o.m_versB.isBranch) return invalid; if (m_versA.isBranch) return m_versA == o.m_versA ? this : invalid; - if (this.path != o.path) return invalid; + // NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer + if (() @trusted { return this.path != o.path; } ()) return invalid; - Version a = m_versA > o.m_versA ? m_versA : o.m_versA; - Version b = m_versB < o.m_versB ? m_versB : o.m_versB; + int acmp = m_versA.opCmp(o.m_versA); + int bcmp = m_versB.opCmp(o.m_versB); Dependency d = this; - d.m_inclusiveA = !m_inclusiveA && m_versA >= o.m_versA ? false : o.m_inclusiveA; - d.m_versA = a; - d.m_inclusiveB = !m_inclusiveB && m_versB <= o.m_versB ? false : o.m_inclusiveB; - d.m_versB = b; + d.m_inclusiveA = !m_inclusiveA && acmp >= 0 ? false : o.m_inclusiveA; + d.m_versA = acmp > 0 ? m_versA : o.m_versA; + d.m_inclusiveB = !m_inclusiveB && bcmp <= 0 ? false : o.m_inclusiveB; + d.m_versB = bcmp < 0 ? m_versB : o.m_versB; d.m_optional = m_optional && o.m_optional; if (!d.valid) return invalid; @@ -553,16 +579,18 @@ // Approximate versions. a = Dependency("~>3.0"); - b = Dependency(">=3.0.0 <4.0.0"); + b = Dependency(">=3.0.0 <4.0.0-0"); assert(a == b, "Testing failed: " ~ a.toString()); assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2"); assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2"); + assert(!a.matches(Version("4.0.0-beta.1"))); a = Dependency("~>3.0.0"); - assert(a == Dependency(">=3.0.0 <3.1.0"), "Testing failed: " ~ a.toString()); + assert(a == Dependency(">=3.0.0 <3.1.0-0"), "Testing failed: " ~ a.toString()); a = Dependency("~>3.5"); - assert(a == Dependency(">=3.5.0 <4.0.0"), "Testing failed: " ~ a.toString()); + assert(a == Dependency(">=3.5.0 <4.0.0-0"), "Testing failed: " ~ a.toString()); a = Dependency("~>3.5.0"); - assert(a == Dependency(">=3.5.0 <3.6.0"), "Testing failed: " ~ a.toString()); + assert(a == Dependency(">=3.5.0 <3.6.0-0"), "Testing failed: " ~ a.toString()); + assert(!Dependency("~>3.0.0").matches(Version("3.1.0-beta"))); a = Dependency("~>0.1.1"); b = Dependency("==0.1.0"); @@ -571,9 +599,11 @@ assert(a.merge(b).valid); b = Dependency("==0.2.0"); assert(!a.merge(b).valid); + b = Dependency("==0.2.0-beta.1"); + assert(!a.merge(b).valid); a = Dependency("~>1.0.1-beta"); - b = Dependency(">=1.0.1-beta <1.1.0"); + b = Dependency(">=1.0.1-beta <1.1.0-0"); assert(a == b, "Testing failed: " ~ a.toString()); assert(a.matches(Version("1.0.1-beta"))); assert(a.matches(Version("1.0.1-beta.6"))); @@ -613,7 +643,7 @@ assert(a.merge(b) == b); assert(b.merge(a) == b); - logDebug("Dependency unittest sucess."); + logDebug("Dependency unittest success."); } unittest { @@ -632,25 +662,26 @@ Semantic Versioning Specification v2.0.0 at http://semver.org/). */ struct Version { +@safe: private { - enum MAX_VERS = "99999.0.0"; - enum UNKNOWN_VERS = "unknown"; + static immutable MAX_VERS = "99999.0.0"; + static immutable UNKNOWN_VERS = "unknown"; + static immutable masterString = "~master"; enum branchPrefix = '~'; - enum masterString = "~master"; string m_version; } - static @property Version minRelease() { return Version("0.0.0"); } - static @property Version maxRelease() { return Version(MAX_VERS); } - static @property Version masterBranch() { return Version(masterString); } - static @property Version unknown() { return Version(UNKNOWN_VERS); } + static immutable Version minRelease = Version("0.0.0"); + static immutable Version maxRelease = Version(MAX_VERS); + static immutable Version masterBranch = Version(masterString); + static immutable Version unknown = Version(UNKNOWN_VERS); /** Constructs a new `Version` from its string representation. */ this(string vers) { enforce(vers.length > 1, "Version strings must not be empty."); - if (vers[0] != branchPrefix && vers != UNKNOWN_VERS) + if (vers[0] != branchPrefix && vers.ptr !is UNKNOWN_VERS.ptr) enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers); m_version = vers; } @@ -662,15 +693,10 @@ */ static Version fromString(string vers) { return Version(vers); } - bool opEquals(const Version oth) const { - if (isUnknown || oth.isUnknown) { - throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, oth)); - } - return opCmp(oth) == 0; - } + bool opEquals(const Version oth) const { return opCmp(oth) == 0; } /// Tests if this represents a branch instead of a version. - @property bool isBranch() const { return !m_version.empty && m_version[0] == branchPrefix; } + @property bool isBranch() const { return m_version.length > 0 && m_version[0] == branchPrefix; } /// Tests if this represents the master branch "~master". @property bool isMaster() const { return m_version == masterString; } diff --git a/source/dub/dependencyresolver.d b/source/dub/dependencyresolver.d index bf5e908..3b61758 100644 --- a/source/dub/dependencyresolver.d +++ b/source/dub/dependencyresolver.d @@ -1,7 +1,7 @@ /** Dependency configuration/version resolution algorithm. - Copyright: © 2014 rejectedsoftware e.K. + Copyright: © 2014-2018 Sönke Ludwig License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Sönke Ludwig */ @@ -11,13 +11,30 @@ import dub.internal.vibecompat.core.log; import std.algorithm : all, canFind, filter, map, sort; -import std.array : appender, array; +import std.array : appender, array, join; import std.conv : to; import std.exception : enforce; +import std.typecons : Nullable; import std.string : format, indexOf, lastIndexOf; +/** Resolves dependency graph with multiple configurations per package. + + The term "configuration" can mean any kind of alternative dependency + configuration of a package. In particular, it can mean different + versions of a package. + + `CONFIG` is an abstract type denoting a single configuration of a certain + package, whereas `CONFIGS` denotes a set of configurations. The + representation of both can be freely chosen, so that `CONFIGS` for example + can be defined in terms of a version range. +*/ class DependencyResolver(CONFIGS, CONFIG) { + /** Encapsulates a list of outgoing edges in the dependency graph. + + A value of this type represents a single dependency with multiple + possible configurations for the target package. + */ static struct TreeNodes { string pack; CONFIGS configs; @@ -36,12 +53,16 @@ } } + /** A single node in the dependency graph. + + Nodes are a combination of a package and a single package configuration. + */ static struct TreeNode { string pack; CONFIG config; hash_t toHash() const nothrow @trusted { - size_t ret = typeid(string).getHash(&pack); + size_t ret = pack.hashOf(); ret ^= typeid(CONFIG).getHash(&config); return ret; } @@ -55,194 +76,31 @@ CONFIG[string] resolve(TreeNode root, bool throw_on_failure = true) { - auto root_base_pack = basePackage(root.pack); + // Leave the possibility to opt-out from the loop limit + import std.process : environment; + bool no_loop_limit = environment.get("DUB_NO_RESOLVE_LIMIT") !is null; - // find all possible configurations of each possible dependency - size_t[string] package_indices; - string[size_t] package_names; - CONFIG[][] all_configs; - bool[] any_config; - bool[string] maybe_optional_deps; - bool[TreeNode] visited; + auto rootbase = root.pack.basePackageName; - void findConfigsRec(TreeNode parent, bool parent_unique) - { - if (parent in visited) return; - visited[parent] = true; + // build up the dependency graph, eliminating as many configurations/ + // versions as possible + ResolveContext context; + context.configs[rootbase] = [ResolveConfig(root.config, true)]; + long loop_counter = no_loop_limit ? long.max : 1_000_000; + constrain(root, context, loop_counter); - foreach (ch; getChildren(parent)) { - auto basepack = basePackage(ch.pack); - auto pidx = all_configs.length; + // remove any non-default optional dependencies + purgeOptionalDependencies(root, context.result); - if (ch.depType != DependencyType.required) maybe_optional_deps[ch.pack] = true; + // the root package is implied by the `root` argument and will not be + // returned explicitly + context.result.remove(rootbase); - CONFIG[] configs; - if (auto pi = basepack in package_indices) { - pidx = *pi; - configs = all_configs[*pi]; - } else { - if (basepack == root_base_pack) configs = [root.config]; - else configs = getAllConfigs(basepack); - all_configs ~= configs; - package_indices[basepack] = pidx; - package_names[pidx] = basepack; - } + logDiagnostic("Dependency resolution result:"); + foreach (d; context.result.keys.sort()) + logDiagnostic(" %s: %s", d, context.result[d]); - foreach (c; getSpecificConfigs(basepack, ch)) - if (!configs.canFind(c)) - configs = c ~ configs; - - if (any_config.length <= pidx) any_config.length = pidx+1; - if (configs.length > 0) - any_config[pidx] = true; - - // eliminate configurations from which we know that they can't satisfy - // the uniquely defined root dependencies (==version or ~branch style dependencies) - if (parent_unique) configs = configs.filter!(c => matches(ch.configs, c)).array; - - all_configs[pidx] = configs; - - foreach (v; configs) - findConfigsRec(TreeNode(ch.pack, v), parent_unique && configs.length == 1); - } - } - findConfigsRec(root, true); - - // append an invalid configuration to denote an unchosen dependency - // this is used to properly support optional dependencies (when - // getChildren() returns no configurations for an optional dependency, - // but getAllConfigs() has already provided an existing list of configs) - foreach (i, ref cfgs; all_configs) - if (cfgs.length == 0 || package_names[i] in maybe_optional_deps) - cfgs = cfgs ~ CONFIG.invalid; - - logDebug("Configurations used for dependency resolution:"); - foreach (n, i; package_indices) logDebug(" %s (%s%s): %s", n, i, n in maybe_optional_deps ? ", maybe optional" : ", required", all_configs[i]); - - auto config_indices = new size_t[all_configs.length]; - config_indices[] = 0; - - string last_error; - - visited = null; - sizediff_t validateConfigs(TreeNode parent, ref string error) - { - import std.algorithm : max; - - if (parent in visited) return -1; - - visited[parent] = true; - sizediff_t maxcpi = -1; - sizediff_t parentidx = package_indices.get(basePackage(parent.pack), -1); - auto parentbase = basePackage(parent.pack); - - // loop over all dependencies - foreach (ch; getChildren(parent)) { - auto basepack = basePackage(ch.pack); - assert(basepack in package_indices, format("%s not in packages %s", basepack, package_indices)); - - // get the current config/version of the current dependency - sizediff_t childidx = package_indices[basepack]; - if (all_configs[childidx] == [CONFIG.invalid]) { - // ignore invalid optional dependencies - if (ch.depType != DependencyType.required) - continue; - - if (parentbase == root_base_pack) { - import std.uni : toLower; - auto lp = ch.pack.toLower(); - if (lp != ch.pack) { - logError("Dependency \"%s\" of %s contains upper case letters, but must be lower case.", ch.pack, parent.pack); - if (getAllConfigs(lp).length) logError("Did you mean \"%s\"?", lp); - } - if (any_config[childidx]) - throw new Exception(format("Root package %s reference %s %s cannot be satisfied.", parent.pack, ch.pack, ch.configs)); - else - throw new Exception(format("Root package %s references unknown package %s", parent.pack, ch.pack)); - } - // choose another parent config to avoid the invalid child - if (parentidx > maxcpi) { - error = format("Package %s contains invalid dependency %s", parent.pack, ch.pack); - logDiagnostic("%s (ci=%s)", error, parentidx); - maxcpi = parentidx; - } - } else { - auto config = all_configs[childidx][config_indices[childidx]]; - auto chnode = TreeNode(ch.pack, config); - - if (config == CONFIG.invalid || !matches(ch.configs, config)) { - // ignore missing optional dependencies - if (config == CONFIG.invalid && ch.depType != DependencyType.required) - continue; - - // if we are at the root level, we can safely skip the maxcpi computation and instead choose another childidx config - if (parentbase == root_base_pack) { - error = format("No match for dependency %s %s of %s", ch.pack, ch.configs, parent.pack); - return childidx; - } - - if (childidx > maxcpi) { - maxcpi = max(childidx, parentidx); - error = format("Dependency %s -> %s %s mismatches with selected version %s", parent.pack, ch.pack, ch.configs, config); - logDebug("%s (ci=%s)", error, maxcpi); - } - - // we know that either the child or the parent needs to be switched - // to another configuration, no need to continue with other children - if (config == CONFIG.invalid) break; - } - - maxcpi = max(maxcpi, validateConfigs(chnode, error)); - } - } - return maxcpi; - } - - string first_error; - - while (true) { - // check if the current combination of configurations works out - visited = null; - string error; - auto conflict_index = validateConfigs(root, error); - if (first_error !is null) first_error = error; - - // print out current iteration state - logDebug("Interation (ci=%s) %s", conflict_index, { - import std.array : join; - auto cs = new string[all_configs.length]; - foreach (p, i; package_indices) { - if (all_configs[i].length) - cs[i] = p~" "~all_configs[i][config_indices[i]].to!string~(i >= 0 && i >= conflict_index ? " (C)" : ""); - else cs[i] = p ~ " [no config]"; - } - return cs.join(", "); - }()); - - if (conflict_index < 0) { - CONFIG[string] ret; - foreach (p, i; package_indices) - if (all_configs[i].length) { - auto cfg = all_configs[i][config_indices[i]]; - if (cfg != CONFIG.invalid) ret[p] = cfg; - } - logDebug("Resolved dependencies before optional-purge: %s", ret.byKey.map!(k => k~" "~ret[k].to!string)); - purgeOptionalDependencies(root, ret); - logDebug("Resolved dependencies after optional-purge: %s", ret.byKey.map!(k => k~" "~ret[k].to!string)); - return ret; - } - - // find the next combination of configurations - foreach_reverse (pi, ref i; config_indices) { - if (pi > conflict_index) i = 0; - else if (++i >= all_configs[pi].length) i = 0; - else break; - } - if (config_indices.all!"a==0") { - if (throw_on_failure) throw new Exception("Could not find a valid dependency tree configuration: "~first_error); - else return null; - } - } + return context.result; } protected abstract CONFIG[] getAllConfigs(string pack); @@ -250,6 +108,154 @@ protected abstract TreeNodes[] getChildren(TreeNode node); protected abstract bool matches(CONFIGS configs, CONFIG config); + private static struct ResolveConfig { + CONFIG config; + bool included; + } + + private static struct ResolveContext { + /** Contains all packages visited by the resolution process so far. + + The key is the qualified name of the package (base + sub) + */ + void[0][string] visited; + + /// The finally chosen configurations for each package + CONFIG[string] result; + + /// The set of available configurations for each package + ResolveConfig[][string] configs; + + /// Determines if a certain package has already been processed + bool isVisited(string package_) const { return (package_ in visited) !is null; } + + /// Marks a package as processed + void setVisited(string package_) { visited[package_] = (void[0]).init; } + + /// Returns a deep clone + ResolveContext clone() + { + ResolveContext ret; + ret.visited = this.visited.dup; + ret.result = this.result.dup; + foreach (pack, cfgs; this.configs) { + ret.configs[pack] = cfgs.dup; + } + return ret; + } + } + + + /** Starting with a single node, fills `context` with a minimized set of + configurations that form valid solutions. + */ + private void constrain(TreeNode n, ref ResolveContext context, ref long max_iterations) + { + auto base = n.pack.basePackageName; + assert(base in context.configs); + if (context.isVisited(n.pack)) return; + context.setVisited(n.pack); + context.result[base] = n.config; + foreach (j, ref sc; context.configs[base]) + sc.included = sc.config == n.config; + + auto dependencies = getChildren(n); + + foreach (dep; dependencies) { + // lazily load all dependency configurations + auto depbase = dep.pack.basePackageName; + auto di = depbase in context.configs; + if (!di) { + context.configs[depbase] = + getAllConfigs(depbase) + .map!(c => ResolveConfig(c, true)) + .array; + di = depbase in context.configs; + } + + // add any dependee defined dependency configurations + foreach (sc; getSpecificConfigs(n.pack, dep)) + if (!(*di).canFind!(c => c.config == sc)) + *di = ResolveConfig(sc, true) ~ *di; + + // restrain the configurations to the current dependency spec + bool any_config = false; + foreach (i, ref c; *di) + if (c.included) { + if (!matches(dep.configs, c.config)) + c.included = false; + else any_config = true; + } + + if (!any_config && dep.depType == DependencyType.required) { + if ((*di).length) + throw new ResolveException(n, dep, context); + else throw new DependencyLoadException(n, dep); + } + } + + constrainDependencies(n, dependencies, 0, context, max_iterations); + } + + /** Recurses back into `constrain` while recursively going through `n`'s + dependencies. + + This attempts to constrain each dependency, while keeping each of them + in a nested stack frame. This allows any errors to properly back + propagate. + */ + private void constrainDependencies(TreeNode n, TreeNodes[] dependencies, size_t depidx, + ref ResolveContext context, ref long max_iterations) + { + if (depidx >= dependencies.length) return; + + assert (--max_iterations > 0, + "The dependency resolution process is taking too long. The" + ~ " dependency graph is likely hitting a pathological case in" + ~ " the resolution algorithm. Please file a bug report at" + ~ " https://github.com/dlang/dub/issues and mention the package" + ~ " recipe that reproduces this error."); + + auto dep = &dependencies[depidx]; + auto depbase = dep.pack.basePackageName; + auto depconfigs = context.configs[depbase]; + + Exception first_err; + + // try each configuration/version of the current dependency + foreach (i, c; depconfigs) { + if (c.included) { + try { + // try the configuration on a cloned context + auto subcontext = context.clone; + constrain(TreeNode(dep.pack, c.config), subcontext, max_iterations); + constrainDependencies(n, dependencies, depidx+1, subcontext, max_iterations); + // if a branch succeeded, replace the current context + // with the one from the branch and return + context = subcontext; + return; + } catch (Exception e) { + if (!first_err) first_err = e; + } + } + } + + // ignore unsatisfiable optional dependencies + if (dep.depType != DependencyType.required) { + auto subcontext = context.clone; + constrainDependencies(n, dependencies, depidx+1, subcontext, max_iterations); + context = subcontext; + return; + } + + // report the first error encountered to the user + if (first_err) throw first_err; + + // should have thrown in constrainRec before reaching this + assert(false, format("Got no configuration for dependency %s %s of %s %s!?", + dep.pack, dep.configs, n.pack, n.config)); + } + private void purgeOptionalDependencies(TreeNode root, ref CONFIG[string] configs) { bool[string] required; @@ -259,9 +265,9 @@ { if (node.pack in visited) return; visited[node.pack] = true; - required[basePackage(node.pack)] = true; + required[node.pack.basePackageName] = true; foreach (dep; getChildren(node).filter!(dep => dep.depType != DependencyType.optional)) - if (auto dp = basePackage(dep.pack) in configs) + if (auto dp = dep.pack.basePackageName in configs) markRecursively(TreeNode(dep.pack, *dp)); } @@ -273,6 +279,57 @@ if (p !in required) configs.remove(p); } + + final class ResolveException : Exception { + import std.range : chain, only; + import std.typecons : tuple; + + string failedNode; + + this(TreeNode parent, TreeNodes dep, in ref ResolveContext context, string file = __FILE__, size_t line = __LINE__) + { + auto m = format("Unresolvable dependencies to package %s:", dep.pack.basePackageName); + super(m, file, line); + + this.failedNode = dep.pack; + + auto failbase = failedNode.basePackageName; + + // get the list of all dependencies to the failed package + auto deps = context.visited.byKey + .filter!(p => p.basePackageName in context.result) + .map!(p => TreeNode(p, context.result[p.basePackageName])) + .map!(n => getChildren(n) + .filter!(d => d.pack.basePackageName == failbase) + .map!(d => tuple(n, d)) + ) + .join + .sort!((a, b) => a[0].pack < b[0].pack); + + foreach (d; deps) { + // filter out trivial self-dependencies + if (d[0].pack.basePackageName == failbase + && matches(d[1].configs, d[0].config)) + continue; + msg ~= format("\n %s %s depends on %s %s", d[0].pack, d[0].config, d[1].pack, d[1].configs); + } + } + } + + final class DependencyLoadException : Exception { + TreeNode parent; + TreeNodes dependency; + + this(TreeNode parent, TreeNodes dep) + { + auto m = format("Failed to find any versions for package %s, referenced by %s %s", + dep.pack, parent.pack, parent.config); + super(m, file, line); + + this.parent = parent; + this.dependency = dep; + } + } } enum DependencyType { @@ -281,14 +338,12 @@ optional } -private string basePackage(string p) +private string basePackageName(string p) { - auto idx = indexOf(p, ":"); - if (idx < 0) return p; - return p[0 .. idx]; + import std.algorithm.searching : findSplit; + return p.findSplit(":")[0]; } - unittest { static struct IntConfig { int value; @@ -392,7 +447,7 @@ assert(res.resolve(TreeNode("a", ic(0))) == ["b":ic(2)], to!string(res.resolve(TreeNode("a", ic(0))))); } - // make sure non-satisfyable dependencies are not a problem, even if non-optional in some dependencies + // make sure non-satisfiable dependencies are not a problem, even if non-optional in some dependencies with (TestResolver) { auto res = new TestResolver([ "a:0": [TreeNodes("b", ics([ic(1), ic(2)]))], @@ -402,4 +457,50 @@ ]); assert(res.resolve(TreeNode("a", ic(0))) == ["b":ic(2)], to!string(res.resolve(TreeNode("a", ic(0))))); } + + // check error message for multiple conflicting dependencies + with (TestResolver) { + auto res = new TestResolver([ + "a:0": [TreeNodes("b", ics([ic(1)])), TreeNodes("c", ics([ic(1)]))], + "b:1": [TreeNodes("d", ics([ic(1)]))], + "c:1": [TreeNodes("d", ics([ic(2)]))], + "d:1": [], + "d:2": [] + ]); + try { + res.resolve(TreeNode("a", ic(0))); + assert(false, "Expected resolve to throw."); + } catch (ResolveException e) { + assert(e.msg == + "Unresolvable dependencies to package d:" + ~ "\n b 1 depends on d [1]" + ~ "\n c 1 depends on d [2]"); + } + } + + // check error message for invalid dependency + with (TestResolver) { + auto res = new TestResolver([ + "a:0": [TreeNodes("b", ics([ic(1)]))] + ]); + try { + res.resolve(TreeNode("a", ic(0))); + assert(false, "Expected resolve to throw."); + } catch (DependencyLoadException e) { + assert(e.msg == "Failed to find any versions for package b, referenced by a 0"); + } + } + + // regression: unresolvable optional dependency skips the remaining dependencies + with (TestResolver) { + auto res = new TestResolver([ + "a:0": [ + TreeNodes("b", ics([ic(2)]), DependencyType.optional), + TreeNodes("c", ics([ic(1)])) + ], + "b:1": [], + "c:1": [] + ]); + assert(res.resolve(TreeNode("a", ic(0))) == ["c":ic(1)]); + } } diff --git a/source/dub/description.d b/source/dub/description.d index c4596a0..7758751 100644 --- a/source/dub/description.d +++ b/source/dub/description.d @@ -28,7 +28,7 @@ PackageDescription[] packages; /// All packages in the dependency tree TargetDescription[] targets; /// Build targets @ignore size_t[string] targetLookup; /// Target index by package name name - + /// Targets by name ref inout(TargetDescription) lookupTarget(string name) inout { @@ -44,10 +44,7 @@ foreach (ref p; packages) if (p.name == name) { - static if (__VERSION__ > 2065) - return p; - else - return *cast(inout(PackageDescription)*)&p; + return p; } throw new Exception("Package '"~name~"' not found in dependency tree."); } @@ -88,6 +85,7 @@ string[] lflags; /// Flags passed to the linker string[] libs; /// Librariy names to link against (typically using "-l") string[] copyFiles; /// Files to copy to the target directory + string[] extraDependencyFiles; /// Files to check for rebuild dub project string[] versions; /// D version identifiers to set string[] debugVersions; /// D debug version identifiers to set string[] importPaths; @@ -96,6 +94,8 @@ string[] postGenerateCommands; /// commands executed after creating the description string[] preBuildCommands; /// Commands to execute prior to every build string[] postBuildCommands; /// Commands to execute after every build + string[] preRunCommands; /// Commands to execute prior to every run + string[] postRunCommands; /// Commands to execute after every run @byName BuildRequirement[] buildRequirements; @byName BuildOption[] options; SourceFileDescription[] files; /// A list of all source/import files possibly used by the package diff --git a/source/dub/dub.d b/source/dub/dub.d index 0290694..d1b6d4c 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -17,23 +17,19 @@ import dub.internal.vibecompat.inet.url; import dub.package_; import dub.packagemanager; -import dub.packagesupplier; +import dub.packagesuppliers; import dub.project; import dub.generators.generator; import dub.init; - -// todo: cleanup imports. import std.algorithm; -import std.array; -import std.conv; -import std.datetime; -import std.exception; +import std.array : array, replace; +import std.conv : to; +import std.exception : enforce; import std.file; -import std.process; +import std.process : environment; +import std.range : assumeSorted, empty; import std.string; -import std.typecons; -import std.zip; import std.encoding : sanitize; // Workaround for libcurl liker errors when building with LDC @@ -63,22 +59,61 @@ registerCompiler(new LDCCompiler); } -/// The URL to the official package registry. -enum defaultRegistryURL = "http://code.dlang.org/"; +deprecated("use defaultRegistryURLs") enum defaultRegistryURL = defaultRegistryURLs[0]; + +/// The URL to the official package registry and it's default fallback registries. +enum defaultRegistryURLs = [ + "https://code.dlang.org/", + "https://code-mirror.dlang.io/", + "https://dub-registry.herokuapp.com/", +]; /** Returns a default list of package suppliers. This will contain a single package supplier that points to the official package registry. - See_Also: `defaultRegistryURL` + See_Also: `defaultRegistryURLs` */ PackageSupplier[] defaultPackageSuppliers() { - logDiagnostic("Using dub registry url '%s'", defaultRegistryURL); - return [new RegistryPackageSupplier(URL(defaultRegistryURL))]; + logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]); + return [new FallbackPackageSupplier(defaultRegistryURLs.map!getRegistryPackageSupplier.array)]; } +/** Returns a registry package supplier according to protocol. + + Allowed protocols are dub+http(s):// and maven+http(s)://. +*/ +PackageSupplier getRegistryPackageSupplier(string url) +{ + switch (url.startsWith("dub+", "mvn+", "file://")) + { + case 1: + return new RegistryPackageSupplier(URL(url[4..$])); + case 2: + return new MavenRegistryPackageSupplier(URL(url[4..$])); + case 3: + return new FileSystemPackageSupplier(NativePath(url[7..$])); + default: + return new RegistryPackageSupplier(URL(url)); + } +} + +unittest +{ + auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org"); + assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); + + dubRegistryPackageSupplier = getRegistryPackageSupplier("https://code.dlang.org"); + assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); + + auto mavenRegistryPackageSupplier = getRegistryPackageSupplier("mvn+http://localhost:8040/maven/libs-release/dubpackages"); + assert(mavenRegistryPackageSupplier.description.canFind(" http://localhost:8040/maven/libs-release/dubpackages")); + + auto fileSystemPackageSupplier = getRegistryPackageSupplier("file:///etc/dubpackages"); + assert(fileSystemPackageSupplier.description.canFind(" " ~ NativePath("/etc/dubpackages").toNativeString)); +} /** Provides a high-level entry point for DUB's functionality. @@ -91,13 +126,14 @@ bool m_dryRun = false; PackageManager m_packageManager; PackageSupplier[] m_packageSuppliers; - Path m_rootPath; + NativePath m_rootPath; SpecialDirs m_dirs; DubConfig m_config; - Path m_projectPath; + NativePath m_projectPath; Project m_project; - Path m_overrideSearchPath; + NativePath m_overrideSearchPath; string m_defaultCompiler; + string m_defaultArchitecture; } /** The default placement location of fetched packages. @@ -125,37 +161,109 @@ this(string root_path = ".", PackageSupplier[] additional_package_suppliers = null, SkipPackageSuppliers skip_registry = SkipPackageSuppliers.none) { - m_rootPath = Path(root_path); - if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath; + m_rootPath = NativePath(root_path); + if (!m_rootPath.absolute) m_rootPath = NativePath(getcwd()) ~ m_rootPath; init(); + if (skip_registry == SkipPackageSuppliers.none) + m_packageSuppliers = getPackageSuppliers(additional_package_suppliers); + else + m_packageSuppliers = getPackageSuppliers(additional_package_suppliers, skip_registry); + + m_packageManager = new PackageManager(m_dirs.localRepository, m_dirs.systemSettings); + + auto ccps = m_config.customCachePaths; + if (ccps.length) + m_packageManager.customCachePaths = ccps; + + updatePackageSearchPath(); + } + + unittest + { + scope (exit) environment.remove("DUB_REGISTRY"); + auto dub = new Dub(".", null, SkipPackageSuppliers.configured); + assert(dub.m_packageSuppliers.length == 0); + environment["DUB_REGISTRY"] = "http://example.com/"; + dub = new Dub(".", null, SkipPackageSuppliers.configured); + assert(dub.m_packageSuppliers.length == 1); + environment["DUB_REGISTRY"] = "http://example.com/;http://foo.com/"; + dub = new Dub(".", null, SkipPackageSuppliers.configured); + assert(dub.m_packageSuppliers.length == 2); + dub = new Dub(".", [new RegistryPackageSupplier(URL("http://bar.com/"))], SkipPackageSuppliers.configured); + assert(dub.m_packageSuppliers.length == 3); + } + + /** Get the list of package suppliers. + + Params: + additional_package_suppliers = A list of package suppliers to try + before the suppliers found in the configurations files and the + `defaultPackageSuppliers`. + skip_registry = Can be used to skip using the configured package + suppliers, as well as the default suppliers. + */ + public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers, SkipPackageSuppliers skip_registry) + { PackageSupplier[] ps = additional_package_suppliers; if (skip_registry < SkipPackageSuppliers.all) - ps ~= m_config.registryURLs - .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url))) + { + ps ~= environment.get("DUB_REGISTRY", null) + .splitter(";") + .map!(url => getRegistryPackageSupplier(url)) .array; + } + + if (skip_registry < SkipPackageSuppliers.configured) + { + ps ~= m_config.registryURLs + .map!(url => getRegistryPackageSupplier(url)) + .array; + } if (skip_registry < SkipPackageSuppliers.standard) ps ~= defaultPackageSuppliers(); - m_packageSuppliers = ps; - m_packageManager = new PackageManager(m_dirs.userSettings, m_dirs.systemSettings); - updatePackageSearchPath(); + return ps; + } + + /// ditto + public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers) + { + return getPackageSuppliers(additional_package_suppliers, m_config.skipRegistry); + } + + unittest + { + scope (exit) environment.remove("DUB_REGISTRY"); + auto dub = new Dub(".", null, SkipPackageSuppliers.none); + + dub.m_config = new DubConfig(Json(["skipRegistry": Json("none")]), null); + assert(dub.getPackageSuppliers(null).length == 1); + + dub.m_config = new DubConfig(Json(["skipRegistry": Json("configured")]), null); + assert(dub.getPackageSuppliers(null).length == 0); + + dub.m_config = new DubConfig(Json(["skipRegistry": Json("standard")]), null); + assert(dub.getPackageSuppliers(null).length == 0); + + environment["DUB_REGISTRY"] = "http://example.com/"; + assert(dub.getPackageSuppliers(null).length == 1); } /** Initializes the instance with a single package search path, without loading a package. This constructor corresponds to the "--bare" option of the command line - interface. Use + interface. Use */ - this(Path override_path) + this(NativePath override_path) { init(); m_overrideSearchPath = override_path; - m_packageManager = new PackageManager(Path(), Path(), false); + m_packageManager = new PackageManager(NativePath(), NativePath(), false); updatePackageSearchPath(); } @@ -163,41 +271,67 @@ { import std.file : tempDir; version(Windows) { - m_dirs.systemSettings = Path(environment.get("ProgramData")) ~ "dub/"; - m_dirs.userSettings = Path(environment.get("APPDATA")) ~ "dub/"; + m_dirs.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/"; + immutable appDataDir = environment.get("APPDATA"); + m_dirs.userSettings = NativePath(appDataDir) ~ "dub/"; + m_dirs.localRepository = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub"; + + migrateRepositoryFromRoaming(m_dirs.userSettings ~"\\packages", m_dirs.localRepository ~ "\\packages"); + } else version(Posix){ - m_dirs.systemSettings = Path("/var/lib/dub/"); - m_dirs.userSettings = Path(environment.get("HOME")) ~ ".dub/"; + m_dirs.systemSettings = NativePath("/var/lib/dub/"); + m_dirs.userSettings = NativePath(environment.get("HOME")) ~ ".dub/"; if (!m_dirs.userSettings.absolute) - m_dirs.userSettings = Path(getcwd()) ~ m_dirs.userSettings; + m_dirs.userSettings = NativePath(getcwd()) ~ m_dirs.userSettings; + m_dirs.localRepository = m_dirs.userSettings; } - m_dirs.temp = Path(tempDir); + m_dirs.temp = NativePath(tempDir); m_config = new DubConfig(jsonFromFile(m_dirs.systemSettings ~ "settings.json", true), m_config); - m_config = new DubConfig(jsonFromFile(Path(thisExePath).parentPath ~ "../etc/dub/settings.json", true), m_config); + m_config = new DubConfig(jsonFromFile(NativePath(thisExePath).parentPath ~ "../etc/dub/settings.json", true), m_config); m_config = new DubConfig(jsonFromFile(m_dirs.userSettings ~ "settings.json", true), m_config); determineDefaultCompiler(); + + m_defaultArchitecture = m_config.defaultArchitecture; } + version(Windows) + private void migrateRepositoryFromRoaming(NativePath roamingDir, NativePath localDir) + { + immutable roamingDirPath = roamingDir.toNativeString(); + if (!existsDirectory(roamingDir)) return; + + immutable localDirPath = localDir.toNativeString(); + logInfo("Detected a package cache in " ~ roamingDirPath ~ ". This will be migrated to " ~ localDirPath ~ ". Please wait..."); + if (!existsDirectory(localDir)) + { + mkdirRecurse(localDirPath); + } + + runCommand(`xcopy /s /e /y "` ~ roamingDirPath ~ `" "` ~ localDirPath ~ `" > NUL`); + rmdirRecurse(roamingDirPath); + } + + @property void dryRun(bool v) { m_dryRun = v; } /** Returns the root path (usually the current working directory). */ - @property Path rootPath() const { return m_rootPath; } + @property NativePath rootPath() const { return m_rootPath; } /// ditto - @property void rootPath(Path root_path) + @property void rootPath(NativePath root_path) { m_rootPath = root_path; - if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath; + if (!m_rootPath.absolute) m_rootPath = NativePath(getcwd()) ~ m_rootPath; } /// Returns the name listed in the dub.json of the current /// application. @property string projectName() const { return m_project.name; } - @property Path projectPath() const { return m_projectPath; } + @property NativePath projectPath() const { return m_projectPath; } @property string[] configurations() const { return m_project.configurations; } @@ -216,6 +350,13 @@ */ @property string defaultCompiler() const { return m_defaultCompiler; } + /** Returns the default architecture to use for building D code. + + If set, the "defaultArchitecture" field of the DUB user or system + configuration file will be used. Otherwise null will be returned. + */ + @property string defaultArchitecture() const { return m_defaultArchitecture; } + /** Loads the package that resides within the configured `rootPath`. */ void loadPackage() @@ -224,7 +365,7 @@ } /// Loads the package from the specified path as the main project package. - void loadPackage(Path path) + void loadPackage(NativePath path) { m_projectPath = path; updatePackageSearchPath(); @@ -243,7 +384,7 @@ Single-file packages are D files that contain a package receipe comment at their top. A recipe comment must be a nested `/+ ... +/` style - comment, containing the virtual recipe file name and a colon, followed by the + comment, containing the virtual recipe file name and a colon, followed by the recipe contents (what would normally be in dub.sdl/dub.json). Example: @@ -269,10 +410,11 @@ The script above can be invoked with "dub --single test.d". */ - void loadSingleFilePackage(Path path) + void loadSingleFilePackage(NativePath path) { import dub.recipe.io : parsePackageRecipe; import std.file : mkdirRecurse, readText; + import std.path : baseName, stripExtension; path = makeAbsolute(path); @@ -295,12 +437,16 @@ recipe_content = file_content[0 .. idx].strip(); } else throw new Exception("The source file must start with a recipe comment."); + auto nidx = recipe_content.indexOf('\n'); + auto idx = recipe_content.indexOf(':'); - enforce(idx > 0, "Missing recipe file name (e.g. \"dub.sdl:\") in recipe comment"); + enforce(idx > 0 && (nidx < 0 || nidx > idx), + "The first line of the recipe comment must list the recipe file name followed by a colon (e.g. \"/+ dub.sdl:\")."); auto recipe_filename = recipe_content[0 .. idx]; recipe_content = recipe_content[idx+1 .. $]; + auto recipe_default_package_name = path.toString.baseName.stripExtension.strip; - auto recipe = parsePackageRecipe(recipe_content, recipe_filename); + auto recipe = parsePackageRecipe(recipe_content, recipe_filename, null, recipe_default_package_name); enforce(recipe.buildSettings.sourceFiles.length == 0, "Single-file packages are not allowed to specify source files."); enforce(recipe.buildSettings.sourcePaths.length == 0, "Single-file packages are not allowed to specify source paths."); enforce(recipe.buildSettings.importPaths.length == 0, "Single-file packages are not allowed to specify import paths."); @@ -317,15 +463,15 @@ /// ditto void loadSingleFilePackage(string path) { - loadSingleFilePackage(Path(path)); + loadSingleFilePackage(NativePath(path)); } /** Disables the default search paths and only searches a specific directory for packages. */ - void overrideSearchPath(Path path) + void overrideSearchPath(NativePath path) { - if (!path.absolute) path = Path(getcwd()) ~ path; + if (!path.absolute) path = NativePath(getcwd()) ~ path; m_overrideSearchPath = path; updatePackageSearchPath(); } @@ -338,8 +484,15 @@ string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); } /** Attempts to upgrade the dependency selection of the loaded project. + + Params: + options = Flags that control how the upgrade is carried out + packages_to_upgrade = Optional list of packages. If this list + contains one or more packages, only those packages will + be upgraded. Otherwise, all packages will be upgraded at + once. */ - void upgrade(UpgradeOptions options) + void upgrade(UpgradeOptions options, string[] packages_to_upgrade = null) { // clear non-existent version selections if (!(options & UpgradeOptions.upgrade)) { @@ -359,7 +512,7 @@ if (versions.canFind!(v => dep.matches(v))) continue next_pack; } catch (Exception e) { - logDiagnostic("Error querying versions for %s, %s: %s", p, ps.description, e.msg); + logWarn("Error querying versions for %s, %s: %s", p, ps.description, e.msg); logDebug("Full error: %s", e.toString().sanitize()); } } @@ -371,19 +524,12 @@ } Dependency[string] versions; - if ((options & UpgradeOptions.useCachedResult) && m_project.isUpgradeCacheUpToDate()) { - logDiagnostic("Using cached upgrade results..."); - versions = m_project.getUpgradeCache(); - } else { - auto resolver = new DependencyVersionResolver(this, options); - versions = resolver.resolve(m_project.rootPackage, m_project.selections); - if (options & UpgradeOptions.useCachedResult) { - logDiagnostic("Caching upgrade results..."); - m_project.setUpgradeCache(versions); - } - } + auto resolver = new DependencyVersionResolver(this, options); + foreach (p; packages_to_upgrade) + resolver.addPackageToUpgrade(p); + versions = resolver.resolve(m_project.rootPackage, m_project.selections); - if (options & UpgradeOptions.printUpgradesOnly) { + if (options & UpgradeOptions.dryRun) { bool any = false; string rootbasename = getBasePackageName(m_project.rootPackage.name); @@ -394,7 +540,7 @@ if (basename == rootbasename) continue; if (!m_project.selections.hasSelectedVersion(basename)) { - logInfo("Non-selected package %s is available with version %s.", + logInfo("Package %s would be selected with version %s.", basename, ver); any = true; continue; @@ -402,7 +548,7 @@ auto sver = m_project.selections.getSelectedVersion(basename); if (!sver.path.empty) continue; if (ver.version_ <= sver.version_) continue; - logInfo("Package %s can be upgraded from %s to %s.", + logInfo("Package %s would be upgraded from %s to %s.", basename, sver, ver); any = true; } @@ -427,28 +573,37 @@ { // TODO: only re-install if there is actually a new commit available logInfo("Re-installing branch based dependency %s %s", p, ver.toString()); - m_packageManager.remove(pack, (options & UpgradeOptions.forceRemove) != 0); + m_packageManager.remove(pack); pack = null; } } FetchOptions fetchOpts; fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none; - fetchOpts |= (options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none; if (!pack) fetch(p, ver, defaultPlacementLocation, fetchOpts, "getting selected version"); if ((options & UpgradeOptions.select) && p != m_project.rootPackage.name) { if (ver.path.empty) m_project.selections.selectVersion(p, ver.version_); else { - Path relpath = ver.path; + NativePath relpath = ver.path; if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path); m_project.selections.selectVersion(p, relpath); } } } + string[] missingDependenciesBeforeReinit = m_project.missingDependencies; m_project.reinit(); - if ((options & UpgradeOptions.select) && !(options & UpgradeOptions.noSaveSelections)) + if (!m_project.hasAllDependencies) { + auto resolvedDependencies = setDifference( + assumeSorted(missingDependenciesBeforeReinit), + assumeSorted(m_project.missingDependencies) + ); + if (!resolvedDependencies.empty) + upgrade(options, m_project.missingDependencies); + } + + if ((options & UpgradeOptions.select) && !(options & (UpgradeOptions.noSaveSelections | UpgradeOptions.dryRun))) m_project.saveSelections(); } @@ -467,13 +622,13 @@ Throws an exception, if unittests failed. */ - void testProject(GeneratorSettings settings, string config, Path custom_main_file) + void testProject(GeneratorSettings settings, string config, NativePath custom_main_file) { - if (custom_main_file.length && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file; + if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file; if (config.length == 0) { // if a custom main file was given, favor the first library configuration, so that it can be applied - if (custom_main_file.length) config = m_project.getDefaultConfiguration(settings.platform, false); + if (!custom_main_file.empty) config = m_project.getDefaultConfiguration(settings.platform, false); // else look for a "unittest" configuration if (!config.length && m_project.rootPackage.configurations.canFind("unittest")) config = "unittest"; // if not found, fall back to the first "library" configuration @@ -484,18 +639,17 @@ auto generator = createProjectGenerator("build", m_project); - auto test_config = format("__test__%s__", config); + auto test_config = format("%s-test-%s", m_project.rootPackage.name.replace(".", "-").replace(":", "-"), config); BuildSettings lbuildsettings = settings.buildSettings; - m_project.addBuildSettings(lbuildsettings, settings.platform, config, null, true); + m_project.addBuildSettings(lbuildsettings, settings, config, null, true); if (lbuildsettings.targetType == TargetType.none) { logInfo(`Configuration '%s' has target type "none". Skipping test.`, config); return; } - if (lbuildsettings.targetType == TargetType.executable) { - if (config == "unittest") logInfo("Running custom 'unittest' configuration.", config); - else logInfo(`Configuration '%s' does not output a library. Falling back to "dub -b unittest -c %s".`, config, config); + if (lbuildsettings.targetType == TargetType.executable && config == "unittest") { + logInfo("Running custom 'unittest' configuration.", config); if (!custom_main_file.empty) logWarn("Ignoring custom main file."); settings.config = config; } else if (lbuildsettings.sourceFiles.empty) { @@ -514,22 +668,38 @@ tcinfo.versions[""] ~= "VibeCustomMain"; m_project.rootPackage.recipe.buildSettings.versions[""] = m_project.rootPackage.recipe.buildSettings.versions.get("", null).remove!(v => v == "VibeDefaultMain"); // TODO: remove this ^ once vibe.d has removed the default main implementation + + auto mainfil = tcinfo.mainSourceFile; + if (!mainfil.length) mainfil = m_project.rootPackage.recipe.buildSettings.mainSourceFile; + string custommodname; - if (custom_main_file.length) { + if (!custom_main_file.empty) { import std.path; tcinfo.sourceFiles[""] ~= custom_main_file.relativeTo(m_project.rootPackage.path).toNativeString(); tcinfo.importPaths[""] ~= custom_main_file.parentPath.toNativeString(); custommodname = custom_main_file.head.toString().baseName(".d"); } + // prepare the list of tested modules string[] import_modules; foreach (file; lbuildsettings.sourceFiles) { - if (file.endsWith(".d") && Path(file).head.toString() != "package.d") - import_modules ~= dub.internal.utils.determineModuleName(lbuildsettings, Path(file), m_project.rootPackage.path); + if (file.endsWith(".d")) { + auto fname = NativePath(file).head.toString(); + if (NativePath(file).relativeTo(m_project.rootPackage.path) == NativePath(mainfil)) { + logWarn("Excluding main source file %s from test.", mainfil); + tcinfo.excludedSourceFiles[""] ~= mainfil; + continue; + } + if (fname == "package.d") { + logWarn("Excluding package.d file from test due to https://issues.dlang.org/show_bug.cgi?id=11847"); + continue; + } + import_modules ~= dub.internal.utils.determineModuleName(lbuildsettings, NativePath(file), m_project.rootPackage.path); + } } // generate main file - Path mainfile = getTempFile("dub_test_root", ".d"); + NativePath mainfile = getTempFile("dub_test_root", ".d"); tcinfo.sourceFiles[""] ~= mainfile.toNativeString(); tcinfo.mainSourceFile = mainfile.toNativeString(); if (!m_dryRun) { @@ -603,7 +773,7 @@ } /// Cleans intermediate/cache files of the given package - void cleanPackage(Path path) + void cleanPackage(NativePath path) { logInfo("Cleaning package at %s...", path.toNativeString()); enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString()); @@ -612,6 +782,14 @@ if (existsFile(path ~ ".dub/build")) rmdirRecurse((path ~ ".dub/build").toNativeString()); if (existsFile(path ~ ".dub/obj")) rmdirRecurse((path ~ ".dub/obj").toNativeString()); + if (existsFile(path ~ ".dub/metadata_cache.json")) std.file.remove((path ~ ".dub/metadata_cache.json").toNativeString()); + + auto p = Package.load(path); + if (p.getBuildSettings().targetType == TargetType.none) { + foreach (sp; p.subPackages.filter!(sp => !sp.path.empty)) { + cleanPackage(path ~ sp.path); + } + } } /// Fetches the package matching the dependency and places it in the specified location. @@ -622,20 +800,22 @@ foreach(ps; m_packageSuppliers){ try { pinfo = ps.fetchPackageRecipe(packageId, dep, (options & FetchOptions.usePrerelease) != 0); + if (pinfo.type == Json.Type.null_) + continue; supplier = ps; break; } catch(Exception e) { - logDiagnostic("Package %s not found for %s: %s", packageId, ps.description, e.msg); + logWarn("Package %s not found for %s: %s", packageId, ps.description, e.msg); logDebug("Full error: %s", e.toString().sanitize()); } } enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency "~dep.toString()); string ver = pinfo["version"].get!string; - Path placement; + NativePath placement; final switch (location) { case PlacementLocation.local: placement = m_rootPath; break; - case PlacementLocation.user: placement = m_dirs.userSettings ~ "packages/"; break; + case PlacementLocation.user: placement = m_dirs.localRepository ~ "packages/"; break; case PlacementLocation.system: placement = m_dirs.systemSettings ~ "packages/"; break; } @@ -662,7 +842,7 @@ return existing; } else { logInfo("Removing %s %s to prepare replacement with a new version.", packageId, ver); - if (!m_dryRun) m_packageManager.remove(existing, (options & FetchOptions.forceRemove) != 0); + if (!m_dryRun) m_packageManager.remove(existing); } } @@ -676,7 +856,7 @@ clean_package_version = clean_package_version.replace("+", "_"); // + has special meaning for Optlink if (!placement.existsFile()) mkdirRecurse(placement.toNativeString()); - Path dstpath = placement ~ (packageId ~ "-" ~ clean_package_version); + NativePath dstpath = placement ~ (packageId ~ "-" ~ clean_package_version); if (!dstpath.existsFile()) mkdirRecurse(dstpath.toNativeString()); @@ -685,6 +865,7 @@ // import path leakage. dstpath = dstpath ~ packageId; + import std.datetime : seconds; auto lock = lockFile(dstpath.toNativeString() ~ ".lock", 30.seconds); // possibly wait for other dub instance if (dstpath.existsFile()) { @@ -692,12 +873,26 @@ return m_packageManager.getPackage(packageId, ver, dstpath); } - auto path = getTempFile(packageId, ".zip"); - supplier.fetchPackage(path, packageId, dep, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail? - scope(exit) std.file.remove(path.toNativeString()); + // repeat download on corrupted zips, see #1336 + foreach_reverse (i; 0..3) + { + import std.zip : ZipException; - logDiagnostic("Placing to %s...", placement.toNativeString()); - return m_packageManager.storeFetchedPackage(path, pinfo, dstpath); + auto path = getTempFile(packageId, ".zip"); + supplier.fetchPackage(path, packageId, dep, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail? + scope(exit) std.file.remove(path.toNativeString()); + logDiagnostic("Placing to %s...", placement.toNativeString()); + + try { + return m_packageManager.storeFetchedPackage(path, pinfo, dstpath); + } catch (ZipException e) { + logInfo("Failed to extract zip archive for %s %s...", packageId, ver); + // rethrow the exception at the end of the loop + if (i == 0) + throw e; + } + } + assert(0, "Should throw a ZipException instead."); } /** Removes a specific locally cached package. @@ -707,13 +902,17 @@ Params: pack = Package instance to remove - force_remove = Forces removal of the package, even if untracked - files are found in its folder. */ - void remove(in Package pack, bool force_remove) + void remove(in Package pack) { logInfo("Removing %s in %s", pack.name, pack.path.toNativeString()); - if (!m_dryRun) m_packageManager.remove(pack, force_remove); + if (!m_dryRun) m_packageManager.remove(pack); + } + + /// Compatibility overload. Use the version without a `force_remove` argument instead. + void remove(in Package pack, bool force_remove) + { + remove(pack); } /// @see remove(string, string, RemoveLocation) @@ -729,11 +928,9 @@ package_id = Name of the package to be removed location_ = Specifies the location to look for the given package name/version. - force_remove = Forces removal of the package, even if untracked - files are found in its folder. resolve_version = Callback to select package version. */ - void remove(string package_id, PlacementLocation location, bool force_remove, + void remove(string package_id, PlacementLocation location, scope size_t delegate(in Package[] packages) resolve_version) { enforce(!package_id.empty); @@ -757,6 +954,9 @@ ~ ")"); } + // Sort package list in ascending version order + packages.sort!((a, b) => a.version_ < b.version_); + immutable idx = resolve_version(packages); if (idx == size_t.max) return; @@ -766,7 +966,7 @@ logDebug("Removing %s packages.", packages.length); foreach(pack; packages) { try { - remove(pack, force_remove); + remove(pack); logInfo("Removed %s, version %s.", package_id, pack.version_); } catch (Exception e) { logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg); @@ -775,6 +975,13 @@ } } + /// Compatibility overload. Use the version without a `force_remove` argument instead. + void remove(string package_id, PlacementLocation location, bool force_remove, + scope size_t delegate(in Package[] packages) resolve_version) + { + remove(package_id, location, resolve_version); + } + /** Removes a specific version of a package. Params: @@ -785,12 +992,10 @@ exception, if there are multiple versions retrieved. location_ = Specifies the location to look for the given package name/version. - force_remove = Forces removal of the package, even if untracked - files are found in its folder. */ - void remove(string package_id, string version_, PlacementLocation location, bool force_remove) + void remove(string package_id, string version_, PlacementLocation location) { - remove(package_id, location, force_remove, (in packages) { + remove(package_id, location, (in packages) { if (version_ == RemoveVersionWildcard) return packages.length; if (version_.empty && packages.length > 1) { @@ -812,6 +1017,12 @@ }); } + /// Compatibility overload. Use the version without a `force_remove` argument instead. + void remove(string package_id, string version_, PlacementLocation location, bool force_remove) + { + remove(package_id, version_, location); + } + /** Adds a directory to the list of locally known packages. Forwards to `PackageManager.addLocalPackage`. @@ -889,8 +1100,16 @@ */ auto searchPackages(string query) { - return m_packageSuppliers.map!(ps => tuple(ps.description, ps.searchPackages(query))).array - .filter!(t => t[1].length); + import std.typecons : Tuple, tuple; + Tuple!(string, PackageSupplier.SearchResult[])[] results; + foreach (ps; this.m_packageSuppliers) { + try + results ~= tuple(ps.description, ps.searchPackages(query)); + catch (Exception e) { + logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg); + } + } + return results.filter!(tup => tup[1].length); } /** Returns a list of all available versions (including branches) for a @@ -907,7 +1126,7 @@ foreach (ps; this.m_packageSuppliers) { try versions ~= ps.getVersions(name); catch (Exception e) { - logDebug("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg); + logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg); } } return versions.sort().uniq.array; @@ -947,9 +1166,10 @@ recipe_callback = Optional callback that can be used to customize the recipe before it gets written. */ - void createEmptyPackage(Path path, string[] deps, string type, + void createEmptyPackage(NativePath path, string[] deps, string type, PackageFormat format = PackageFormat.sdl, - scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null) + scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null, + string[] app_args = []) { if (!path.absolute) path = m_rootPath ~ path; path.normalize(); @@ -977,10 +1197,40 @@ initPackage(path, depVers, type, format, recipe_callback); + if (!["vibe.d", "deimos", "minimal"].canFind(type)) { + runCustomInitialization(path, type, app_args); + } + //Act smug to the user. logInfo("Successfully created an empty project in '%s'.", path.toNativeString()); } + private void runCustomInitialization(NativePath path, string type, string[] runArgs) + { + string packageName = type; + auto template_pack = m_packageManager.getBestPackage(packageName, ">=0.0.0"); + if (!template_pack) template_pack = m_packageManager.getBestPackage(packageName, "~master"); + if (!template_pack) { + logInfo("%s is not present, getting and storing it user wide", packageName); + template_pack = fetch(packageName, Dependency(">=0.0.0"), defaultPlacementLocation, FetchOptions.none); + } + + Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false); + auto template_dub = new Dub(null, m_packageSuppliers); + template_dub.loadPackage(initSubPackage); + auto compiler_binary = this.defaultCompiler; + + GeneratorSettings settings; + settings.config = "application"; + settings.compiler = getCompiler(compiler_binary); + settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary, m_defaultArchitecture); + settings.buildType = "debug"; + settings.run = true; + settings.runArgs = runArgs; + initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString(); + template_dub.generateProject("build", settings); + } + /** Converts the package recipe of the loaded root package to the given format. Params: @@ -1001,13 +1251,13 @@ } auto srcfile = m_project.rootPackage.recipePath; - auto srcext = srcfile[$-1].toString().extension; + auto srcext = srcfile.head.toString().extension; if (srcext == "."~destination_file_ext) { logInfo("Package format is already %s.", destination_file_ext); return; } - writePackageRecipe(srcfile[0 .. $-1] ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe); + writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe); removeFile(srcfile); } @@ -1021,6 +1271,8 @@ */ void runDdox(bool run, string[] generate_args = null) { + import std.process : browse; + if (m_dryRun) return; // allow to choose a custom ddox tool @@ -1030,7 +1282,7 @@ auto tool_pack = m_packageManager.getBestPackage(tool, ">=0.0.0"); if (!tool_pack) tool_pack = m_packageManager.getBestPackage(tool, "~master"); if (!tool_pack) { - logInfo("% is not present, getting and storing it user wide", tool); + logInfo("%s is not present, getting and storing it user wide", tool); tool_pack = fetch(tool, Dependency(">=0.0.0"), defaultPlacementLocation, FetchOptions.none); } @@ -1043,7 +1295,7 @@ GeneratorSettings settings; settings.config = "application"; settings.compiler = getCompiler(compiler_binary); // TODO: not using --compiler ??? - settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary); + settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary, m_defaultArchitecture); settings.buildType = "debug"; settings.run = true; @@ -1067,23 +1319,23 @@ if (!run) { // TODO: ddox should copy those files itself - version(Windows) runCommand("xcopy /S /D "~tool_path~"public\\* docs\\"); + version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`); else runCommand("rsync -ru '"~tool_path~"public/' docs/"); } } private void updatePackageSearchPath() { - if (m_overrideSearchPath.length) { + if (!m_overrideSearchPath.empty) { m_packageManager.disableDefaultSearchPaths = true; m_packageManager.searchPath = [m_overrideSearchPath]; } else { auto p = environment.get("DUBPATH"); - Path[] paths; + NativePath[] paths; version(Windows) enum pathsep = ";"; else enum pathsep = ":"; - if (p.length) paths ~= p.split(pathsep).map!(p => Path(p))().array(); + if (p.length) paths ~= p.split(pathsep).map!(p => NativePath(p))().array(); m_packageManager.disableDefaultSearchPaths = false; m_packageManager.searchPath = paths; } @@ -1091,23 +1343,62 @@ private void determineDefaultCompiler() { + import std.file : thisExePath; + import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator; import std.process : environment; + import std.range : front; - m_defaultCompiler = m_config.defaultCompiler; - if (m_defaultCompiler.length) return; + m_defaultCompiler = m_config.defaultCompiler.expandTilde; + if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute) + return; + + static immutable BinaryPrefix = `$DUB_BINARY_PATH`; + if(m_defaultCompiler.startsWith(BinaryPrefix)) + { + m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $]; + return; + } + + if (!find!isDirSeparator(m_defaultCompiler).empty) + throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~ + "\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead."); version (Windows) enum sep = ";", exe = ".exe"; version (Posix) enum sep = ":", exe = ""; auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; + // If a compiler name is specified, look for it next to dub. + // Otherwise, look for any of the common compilers adjacent to dub. + if (m_defaultCompiler.length) + { + string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe); + if (existsFile(compilerPath)) + { + m_defaultCompiler = compilerPath; + return; + } + } + else + { + auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe))); + if (!nextFound.empty) + { + m_defaultCompiler = buildPath(thisExePath().dirName(), nextFound.front ~ exe); + return; + } + } - auto paths = environment.get("PATH", "").splitter(sep).map!Path; + // If nothing found next to dub, search the user's PATH, starting + // with the compiler name from their DUB config file, if specified. + if (m_defaultCompiler.length) + compilers = m_defaultCompiler ~ compilers; + auto paths = environment.get("PATH", "").splitter(sep).map!NativePath; auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe)))); m_defaultCompiler = res.empty ? compilers[0] : res.front; } - private Path makeAbsolute(Path p) const { return p.absolute ? p : m_rootPath ~ p; } - private Path makeAbsolute(string p) const { return makeAbsolute(Path(p)); } + private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; } + private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); } } @@ -1117,7 +1408,7 @@ none = 0, forceBranchUpgrade = 1<<0, usePrerelease = 1<<1, - forceRemove = 1<<2, + forceRemove = 1<<2, /// Deprecated, does nothing. printOnly = 1<<3, } @@ -1127,18 +1418,20 @@ none = 0, upgrade = 1<<1, /// Upgrade existing packages preRelease = 1<<2, /// inclde pre-release versions in upgrade - forceRemove = 1<<3, /// Force removing package folders, which contain unknown files + forceRemove = 1<<3, /// Deprecated, does nothing. select = 1<<4, /// Update the dub.selections.json file with the upgraded versions - printUpgradesOnly = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence - useCachedResult = 1<<6, /// Use cached information stored with the package to determine upgrades + dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence + /*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead + /*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect noSaveSelections = 1<<7, /// Don't store updated selections on disk } /// Determines which of the default package suppliers are queried for packages. enum SkipPackageSuppliers { - none, /// Uses all configured package suppliers. - standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`). - all /// Uses only manually specified package suppliers. + none, /// Uses all configured package suppliers. + standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`). + configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file + all /// Uses only manually specified package suppliers. } private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { @@ -1149,6 +1442,9 @@ Package[string] m_remotePackages; SelectedVersions m_selectedVersions; Package m_rootPackage; + bool[string] m_packagesToUpgrade; + Package[PackageDependency] m_packages; + TreeNodes[][TreeNode] m_children; } @@ -1158,6 +1454,11 @@ m_options = options; } + void addPackageToUpgrade(string name) + { + m_packagesToUpgrade[name] = true; + } + Dependency[string] resolve(Package root, SelectedVersions selected_versions) { m_rootPackage = root; @@ -1165,12 +1466,17 @@ return super.resolve(TreeNode(root.name, Dependency(root.version_)), (m_options & UpgradeOptions.printUpgradesOnly) == 0); } + protected bool isFixedPackage(string pack) + { + return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade; + } + protected override Dependency[] getAllConfigs(string pack) { if (auto pvers = pack in m_packageVersions) return *pvers; - if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(pack)) { + if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) { auto ret = [m_selectedVersions.getSelectedVersion(pack)]; logDiagnostic("Using fixed selection %s %s", pack, ret[0]); m_packageVersions[pack] = ret; @@ -1194,7 +1500,7 @@ versions ~= vers; break; } catch (Exception e) { - logDebug("Package %s not found in %s: %s", pack, ps.description, e.msg); + logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg); logDebug("Full error: %s", e.toString().sanitize); } } @@ -1230,6 +1536,16 @@ protected override TreeNodes[] getChildren(TreeNode node) { + if (auto pc = node in m_children) + return *pc; + auto ret = getChildrenRaw(node); + m_children[node] = ret; + return ret; + } + + private final TreeNodes[] getChildrenRaw(TreeNode node) + { + import std.array : appender; auto ret = appender!(TreeNodes[]); auto pack = getPackage(node.pack, node.config); if (!pack) { @@ -1239,7 +1555,7 @@ } auto basepack = pack.basePackage; - foreach (d; pack.getAllDependencies()) { + foreach (d; pack.getAllDependenciesRange()) { auto dbasename = getBasePackageName(d.name); // detect dependencies to the root package (or sub packages thereof) @@ -1291,6 +1607,16 @@ private Package getPackage(string name, Dependency dep) { + auto key = PackageDependency(name, dep); + if (auto pp = key in m_packages) + return *pp; + auto p = getPackageRaw(name, dep); + m_packages[key] = p; + return p; + } + + private Package getPackageRaw(string name, Dependency dep) + { auto basename = getBasePackageName(name); // for sub packages, first try to get them from the base package @@ -1304,7 +1630,7 @@ // note: external sub packages are handled further below auto spr = basepack.getInternalSubPackage(subname); if (!spr.isNull) { - auto sp = new Package(spr, basepack.path, basepack); + auto sp = new Package(spr.get, basepack.path, basepack); m_remotePackages[sp.name] = sp; return sp; } else { @@ -1349,6 +1675,8 @@ if (rootpack == name) { try { auto desc = ps.fetchPackageRecipe(name, dep, prerelease); + if (desc.type == Json.Type.null_) + continue; auto ret = new Package(desc); m_remotePackages[key] = ret; return ret; @@ -1361,7 +1689,6 @@ try { FetchOptions fetchOpts; fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none; - fetchOpts |= (m_options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none; m_dub.fetch(rootpack, dep, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description"); auto ret = m_dub.m_packageManager.getBestPackage(name, dep); if (!ret) { @@ -1385,9 +1712,10 @@ } private struct SpecialDirs { - Path temp; - Path userSettings; - Path systemSettings; + NativePath temp; + NativePath userSettings; + NativePath systemSettings; + NativePath localRepository; } private class DubConfig { @@ -1411,6 +1739,32 @@ return ret; } + @property SkipPackageSuppliers skipRegistry() + { + if(auto pv = "skipRegistry" in m_data) + return to!SkipPackageSuppliers((*pv).get!string); + + if (m_parentConfig) + return m_parentConfig.skipRegistry; + + return SkipPackageSuppliers.none; + } + + @property NativePath[] customCachePaths() + { + import std.algorithm.iteration : map; + import std.array : array; + + NativePath[] ret; + if (auto pv = "customCachePaths" in m_data) + ret = (*pv).deserializeJson!(string[]) + .map!(s => NativePath(s)) + .array; + if (m_parentConfig) + ret ~= m_parentConfig.customCachePaths; + return ret; + } + @property string defaultCompiler() const { if (auto pv = "defaultCompiler" in m_data) @@ -1418,4 +1772,12 @@ if (m_parentConfig) return m_parentConfig.defaultCompiler; return null; } + + @property string defaultArchitecture() + const { + if(auto pv = "defaultArchitecture" in m_data) + return (*pv).get!string; + if (m_parentConfig) return m_parentConfig.defaultArchitecture; + return null; + } } diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 7d1e472..4414bb8 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -33,8 +33,7 @@ class BuildGenerator : ProjectGenerator { private { PackageManager m_packageMan; - Path[] m_temporaryFiles; - Path m_targetExecutablePath; + NativePath[] m_temporaryFiles; } this(Project project) @@ -47,12 +46,29 @@ { scope (exit) cleanupTemporaries(); + auto dcl = settings.compiler; + + void checkPkgRequirements(const(Package) pkg) + { + const tr = pkg.recipe.toolchainRequirements; + tr.checkPlatform(settings.platform, pkg.name); + } + + checkPkgRequirements(m_project.rootPackage); + foreach (pkg; m_project.dependencies) + checkPkgRequirements(pkg); + + auto root_ti = targets[m_project.rootPackage.name]; + + enforce(!(settings.rdmd && root_ti.buildSettings.targetType == TargetType.none), + "Building package with target type \"none\" with rdmd is not supported yet."); + logInfo("Performing \"%s\" build using %s for %-(%s, %).", settings.buildType, settings.platform.compilerBinary, settings.platform.architecture); bool any_cached = false; - Path[string] target_paths; + NativePath[string] target_paths; bool[string] visited; void buildTargetRec(string target) @@ -65,28 +81,28 @@ foreach (dep; ti.dependencies) buildTargetRec(dep); - Path[] additional_dep_files; + NativePath[] additional_dep_files; auto bs = ti.buildSettings.dup; foreach (ldep; ti.linkDependencies) { auto dbs = targets[ldep].buildSettings; - if (bs.targetType != TargetType.staticLibrary) { + if (bs.targetType != TargetType.staticLibrary && !(bs.options & BuildOption.syntaxOnly)) { bs.addSourceFiles(target_paths[ldep].toNativeString()); } else { additional_dep_files ~= target_paths[ldep]; } } - Path tpath; - if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, tpath)) - any_cached = true; + NativePath tpath; + if (bs.targetType != TargetType.none) + if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, tpath)) + any_cached = true; target_paths[target] = tpath; } // build all targets - auto root_ti = targets[m_project.rootPackage.name]; if (settings.rdmd || root_ti.buildSettings.targetType == TargetType.staticLibrary) { // RDMD always builds everything at once and static libraries don't need their // dependencies to be built - Path tpath; + NativePath tpath; buildTarget(settings, root_ti.buildSettings.dup, m_project.rootPackage, root_ti.config, root_ti.packages, null, tpath); } else { buildTargetRec(m_project.rootPackage.name); @@ -102,29 +118,24 @@ // run the generated executable auto buildsettings = targets[m_project.rootPackage.name].buildSettings.dup; if (settings.run && !(buildsettings.options & BuildOption.syntaxOnly)) { - Path exe_file_path; - if (!m_targetExecutablePath.length) + NativePath exe_file_path; + if (m_tempTargetExecutablePath.empty) exe_file_path = getTargetPath(buildsettings, settings); else - exe_file_path = m_targetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); + exe_file_path = m_tempTargetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); runTarget(exe_file_path, buildsettings, settings.runArgs, settings); } } - private bool buildTarget(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, in Package[] packages, in Path[] additional_dep_files, out Path target_path) + private bool buildTarget(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_path) { - auto cwd = Path(getcwd()); + auto cwd = NativePath(getcwd()); bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); auto build_id = computeBuildID(config, buildsettings, settings); // make all paths relative to shrink the command line - string makeRelative(string path) { - auto p = Path(path); - // storing in a separate temprary to work around #601 - auto prel = p.absolute ? p.relativeTo(cwd) : p; - return prel.toNativeString(); - } + string makeRelative(string path) { return shrinkPath(NativePath(path), cwd); } foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f); foreach (ref p; buildsettings.importPaths) p = makeRelative(p); foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); @@ -136,7 +147,7 @@ else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages, additional_dep_files, target_path); // HACK: cleanup dummy doc files, we shouldn't specialize on buildType - // here and the compiler shouldn't need dummy doc ouput. + // here and the compiler shouldn't need dummy doc output. if (settings.buildType == "ddox") { if ("__dummy.html".exists) removeFile("__dummy.html"); @@ -154,12 +165,15 @@ } private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, - string build_id, in Package[] packages, in Path[] additional_dep_files, out Path target_binary_path) + string build_id, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_binary_path) { - auto cwd = Path(getcwd()); + auto cwd = NativePath(getcwd()); - Path target_path; - if (settings.tempBuild) m_targetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", pack.name, pack.version_, build_id); + NativePath target_path; + if (settings.tempBuild) { + string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name; + m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id); + } else target_path = pack.path ~ format(".dub/build/%s/", build_id); if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) { @@ -190,7 +204,7 @@ // override target path auto cbuildsettings = buildsettings; - cbuildsettings.targetPath = target_path.relativeTo(cwd).toNativeString(); + cbuildsettings.targetPath = shrinkPath(target_path, cwd); buildWithCompiler(settings, cbuildsettings); target_binary_path = getTargetPath(cbuildsettings, settings); @@ -200,14 +214,14 @@ return false; } - private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out Path target_path) + private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { - auto cwd = Path(getcwd()); - //Added check for existance of [AppNameInPackagejson].d + auto cwd = NativePath(getcwd()); + //Added check for existence of [AppNameInPackagejson].d //If exists, use that as the starting file. - Path mainsrc; + NativePath mainsrc; if (buildsettings.mainSourceFile.length) { - mainsrc = Path(buildsettings.mainSourceFile); + mainsrc = NativePath(buildsettings.mainSourceFile); if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc; } else { mainsrc = getMainSourceFile(pack); @@ -225,7 +239,7 @@ // or with "/" instead of "\" bool tmp_target = false; if (generate_binary) { - if (settings.tempBuild || (settings.run && !isWritableDir(Path(buildsettings.targetPath), true))) { + if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; auto tmpdir = getTempDir()~".rdmd/source/"; @@ -261,20 +275,20 @@ if (tmp_target) { m_temporaryFiles ~= target_path; foreach (f; buildsettings.copyFiles) - m_temporaryFiles ~= Path(buildsettings.targetPath).parentPath ~ Path(f).head; + m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; } } - private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out Path target_path) + private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { - auto cwd = Path(getcwd()); + auto cwd = NativePath(getcwd()); auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; // make file paths relative to shrink the command line foreach (ref f; buildsettings.sourceFiles) { - auto fp = Path(f); + auto fp = NativePath(f); if( fp.absolute ) fp = fp.relativeTo(cwd); f = fp.toNativeString(); } @@ -283,7 +297,7 @@ // make all target/import paths relative string makeRelative(string path) { - auto p = Path(path); + auto p = NativePath(path); // storing in a separate temprary to work around #601 auto prel = p.absolute ? p.relativeTo(cwd) : p; return prel.toNativeString(); @@ -294,7 +308,7 @@ bool is_temp_target = false; if (generate_binary) { - if (settings.tempBuild || (settings.run && !isWritableDir(Path(buildsettings.targetPath), true))) { + if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)); auto tmppath = getTempDir()~("dub/"~rnd~"/"); @@ -315,13 +329,13 @@ if (is_temp_target) { m_temporaryFiles ~= target_path; foreach (f; buildsettings.copyFiles) - m_temporaryFiles ~= Path(buildsettings.targetPath).parentPath ~ Path(f).head; + m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; } } private string computeBuildID(string config, in BuildSettings buildsettings, GeneratorSettings settings) { - import std.digest.digest; + import std.digest; import std.digest.md; import std.bitmanip; @@ -349,17 +363,17 @@ settings.platform.compiler, settings.platform.frontendVersion, hashstr); } - private void copyTargetFile(Path build_path, BuildSettings buildsettings, GeneratorSettings settings) + private void copyTargetFile(NativePath build_path, BuildSettings buildsettings, GeneratorSettings settings) { auto filename = settings.compiler.getTargetFileName(buildsettings, settings.platform); auto src = build_path ~ filename; logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath); - if (!existsFile(Path(buildsettings.targetPath))) + if (!existsFile(NativePath(buildsettings.targetPath))) mkdirRecurse(buildsettings.targetPath); - hardLinkFile(src, Path(buildsettings.targetPath) ~ filename, true); + hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true); } - private bool isUpToDate(Path target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in Path[] additional_dep_files) + private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files) { import std.datetime; @@ -374,11 +388,12 @@ allfiles ~= buildsettings.sourceFiles; allfiles ~= buildsettings.importFiles; allfiles ~= buildsettings.stringImportFiles; + allfiles ~= buildsettings.extraDependencyFiles; // TODO: add library files foreach (p; packages) - allfiles ~= (p.recipePath != Path.init ? p : p.basePackage).recipePath.toNativeString(); + allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString(); foreach (f; additional_dep_files) allfiles ~= f.toNativeString(); - if (main_pack is m_project.rootPackage) + if (main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0) allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString(); foreach (file; allfiles.data) { @@ -403,13 +418,17 @@ static string pathToObjName(string path) { - import std.path : buildNormalizedPath, dirSeparator, stripDrive; - return stripDrive(buildNormalizedPath(getcwd(), path~objSuffix))[1..$].replace(dirSeparator, "."); + import std.digest.crc : crc32Of; + import std.path : buildNormalizedPath, dirSeparator, relativePath, stripDrive; + if (path.endsWith(".d")) path = path[0 .. $-2]; + auto ret = buildNormalizedPath(getcwd(), path).replace(dirSeparator, "."); + auto idx = ret.lastIndexOf('.'); + return idx < 0 ? ret ~ objSuffix : format("%s_%(%02x%)%s", ret[idx+1 .. $], crc32Of(ret[0 .. idx]), objSuffix); } /// Compile a single source file (srcFile), and write the object to objName. static string compileUnit(string srcFile, string objName, BuildSettings bs, GeneratorSettings gs) { - Path tempobj = Path(bs.targetPath)~objName; + NativePath tempobj = NativePath(bs.targetPath)~objName; string objPath = tempobj.toNativeString(); bs.libs = null; bs.lflags = null; @@ -426,7 +445,7 @@ auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; - Path target_file; + NativePath target_file; scope (failure) { logDiagnostic("FAIL %s %s %s" , buildsettings.targetPath, buildsettings.targetName, buildsettings.targetType); auto tpath = getTargetPath(buildsettings, settings); @@ -462,48 +481,51 @@ on the other compilers. Later this should be integrated somehow in the build process (either in the dub.json, or using a command line flag) */ - } else if (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name != "dmd" || !generate_binary || is_static_library) { - // setup for command line - if (generate_binary) settings.compiler.setTarget(buildsettings, settings.platform); - settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); - + } else if (generate_binary && (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name != "dmd" || is_static_library)) { // don't include symbols of dependencies (will be included by the top level target) if (is_static_library) buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !f.isLinkerFile()).array; + // setup for command line + settings.compiler.setTarget(buildsettings, settings.platform); + settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); + // invoke the compiler settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); } else { // determine path for the temporary object file string tempobjname = buildsettings.targetName ~ objSuffix; - Path tempobj = Path(buildsettings.targetPath) ~ tempobjname; + NativePath tempobj = NativePath(buildsettings.targetPath) ~ tempobjname; // setup linker command line auto lbuildsettings = buildsettings; lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => isLinkerFile(f)).array; - settings.compiler.setTarget(lbuildsettings, settings.platform); + if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform); settings.compiler.prepareBuildSettings(lbuildsettings, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); // setup compiler command line buildsettings.libs = null; buildsettings.lflags = null; - buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString()); + if (generate_binary) buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString()); buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(f)).array; + settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); - logInfo("Linking..."); - settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback); + if (generate_binary) { + logInfo("Linking..."); + settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback); + } } } - private void runTarget(Path exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings) + private void runTarget(NativePath exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings) { if (buildsettings.targetType == TargetType.executable) { - auto cwd = Path(getcwd()); + auto cwd = NativePath(getcwd()); auto runcwd = cwd; if (buildsettings.workingDirectory.length) { - runcwd = Path(buildsettings.workingDirectory); + runcwd = NativePath(buildsettings.workingDirectory); if (!runcwd.absolute) runcwd = cwd ~ runcwd; logDiagnostic("Switching to %s", runcwd.toNativeString()); chdir(runcwd.toNativeString()); @@ -519,19 +541,42 @@ if (!exe_path_string.startsWith(".") && (exe_path_string.length < 2 || exe_path_string[1] != ':')) exe_path_string = ".\\" ~ exe_path_string; } + runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings); logInfo("Running %s %s", exe_path_string, run_args.join(" ")); if (settings.runCallback) { auto res = execute(exe_path_string ~ run_args); settings.runCallback(res.status, res.output); + settings.targetExitStatus = res.status; + runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); } else { auto prg_pid = spawnProcess(exe_path_string ~ run_args); auto result = prg_pid.wait(); + settings.targetExitStatus = result; + runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); enforce(result == 0, "Program exited with code "~to!string(result)); } } else enforce(false, "Target is a library. Skipping execution."); } + private void runPreRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, + in BuildSettings buildsettings) + { + if (buildsettings.preRunCommands.length) { + logInfo("Running pre-run commands..."); + runBuildCommands(buildsettings.preRunCommands, pack, proj, settings, buildsettings); + } + } + + private void runPostRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, + in BuildSettings buildsettings) + { + if (buildsettings.postRunCommands.length) { + logInfo("Running post-run commands..."); + runBuildCommands(buildsettings.postRunCommands, pack, proj, settings, buildsettings); + } + } + private void cleanupTemporaries() { foreach_reverse (f; m_temporaryFiles) { @@ -547,7 +592,7 @@ } } -private Path getMainSourceFile(in Package prj) +private NativePath getMainSourceFile(in Package prj) { foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"]) if (existsFile(prj.path ~ f)) @@ -555,7 +600,57 @@ return prj.path ~ "source/app.d"; } -private Path getTargetPath(in ref BuildSettings bs, in ref GeneratorSettings settings) +private NativePath getTargetPath(in ref BuildSettings bs, in ref GeneratorSettings settings) { - return Path(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform); + return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform); +} + +private string shrinkPath(NativePath path, NativePath base) +{ + auto orig = path.toNativeString(); + if (!path.absolute) return orig; + auto ret = path.relativeTo(base).toNativeString(); + return ret.length < orig.length ? ret : orig; +} + +unittest { + assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString()); + assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString()); + assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString()); + assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString()); +} + +unittest { // issue #1235 - pass no library files to compiler command line when building a static lib + import dub.internal.vibecompat.data.json : parseJsonString; + import dub.compilers.gdc : GDCCompiler; + import dub.platform : determinePlatform; + + version (Windows) auto libfile = "bar.lib"; + else auto libfile = "bar.a"; + + auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`); + auto pack = new Package(desc, NativePath("/tmp/fooproject")); + auto pman = new PackageManager(NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); + auto prj = new Project(pman, pack); + + final static class TestCompiler : GDCCompiler { + override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) { + assert(!settings.dflags[].any!(f => f.canFind("bar"))); + } + override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { + assert(false); + } + } + + auto comp = new TestCompiler; + + GeneratorSettings settings; + settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075); + settings.compiler = new TestCompiler; + settings.config = "library"; + settings.buildType = "debug"; + settings.tempBuild = true; + + auto gen = new BuildGenerator(prj); + gen.generate(settings); } diff --git a/source/dub/generators/cmake.d b/source/dub/generators/cmake.d index fcf0b94..595442d 100644 --- a/source/dub/generators/cmake.d +++ b/source/dub/generators/cmake.d @@ -26,53 +26,53 @@ { super(project); } - + override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) { auto script = appender!(char[]); auto scripts = appender!(string[]); bool[string] visited; - Path projectRoot = m_project.rootPackage.path; - Path cmakeListsPath = projectRoot ~ "CMakeLists.txt"; - + NativePath projectRoot = m_project.rootPackage.path; + NativePath cmakeListsPath = projectRoot ~ "CMakeLists.txt"; + foreach(name, info; targets) { if(visited.get(name, false)) continue; - + visited[name] = true; name = name.sanitize; string targetType; string libType; bool addTarget = true; - + switch(info.buildSettings.targetType) with(TargetType) { case autodetect: throw new Exception("Don't know what to do about autodetect target type"); case executable: targetType = "executable"; - + break; case dynamicLibrary: libType = "SHARED"; - + goto case; case library: case staticLibrary: targetType = "library"; - + break; case sourceLibrary: addTarget = false; - + break; case none: continue; default: assert(false); } - + script.put("include(UseD)\n"); script.put( "add_d_conditions(VERSION %s DEBUG %s)\n".format( @@ -80,17 +80,17 @@ info.buildSettings.debugVersions.dup.join(" "), ) ); - + foreach(directory; info.buildSettings.importPaths) script.put("include_directories(%s)\n".format(directory.sanitizeSlashes)); - + if(addTarget) { script.put("add_%s(%s %s\n".format(targetType, name, libType)); - + foreach(file; info.buildSettings.sourceFiles) script.put(" %s\n".format(file.sanitizeSlashes)); - + script.put(")\n"); script.put( "target_link_libraries(%s %s %s)\n".format( @@ -106,16 +106,16 @@ ) ~ "\n" ); } - + string filename = (projectRoot ~ "%s.cmake".format(name)).toNativeString; File file = File(filename, "w"); - + file.write(script.data); file.close; script.shrinkTo(0); scripts.put(filename); } - + if(!cmakeListsPath.existsFile) { logWarn("You must use a fork of CMake which has D support for these scripts to function properly."); @@ -123,12 +123,12 @@ logInfo("Generating default CMakeLists.txt"); script.put("cmake_minimum_required(VERSION 3.0)\n"); script.put("project(%s D)\n".format(m_project.rootPackage.name)); - + foreach(path; scripts.data) script.put("include(%s)\n".format(path)); - + File file = File(cmakeListsPath.toNativeString, "w"); - + file.write(script.data); file.close; } diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index cb82072..ddbbc33 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -74,6 +74,7 @@ protected { Project m_project; + NativePath m_tempTargetExecutablePath; } this(Project project) @@ -89,30 +90,32 @@ if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); - TargetInfo[string] targets; string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); + TargetInfo[string] targets; foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { - BuildSettings buildsettings; - buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); - prepareGeneration(pack, m_project, settings, buildsettings); + BuildSettings buildSettings; + auto config = configs[pack.name]; + buildSettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, config), settings, true); + targets[pack.name] = TargetInfo(pack, [pack], config, buildSettings); + + prepareGeneration(pack, m_project, settings, buildSettings); } - string[] mainfiles; - collect(settings, m_project.rootPackage, targets, configs, mainfiles, null); - downwardsInheritSettings(m_project.rootPackage.name, targets, targets[m_project.rootPackage.name].buildSettings); + configurePackages(m_project.rootPackage, targets, settings); + addBuildTypeSettings(targets, settings); foreach (ref t; targets.byValue) enforceBuildRequirements(t.buildSettings); - auto bs = &targets[m_project.rootPackage.name].buildSettings; - if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles); generateTargets(settings, targets); foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { BuildSettings buildsettings; - buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); + buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), settings, true); bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); - finalizeGeneration(pack, m_project, settings, buildsettings, Path(bs.targetPath), generate_binary); + auto bs = &targets[m_project.rootPackage.name].buildSettings; + auto targetPath = (m_tempTargetExecutablePath.empty) ? NativePath(bs.targetPath) : m_tempTargetExecutablePath; + finalizeGeneration(pack, m_project, settings, buildsettings, targetPath, generate_binary); } performPostGenerateActions(settings, targets); @@ -143,141 +146,411 @@ */ protected void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) {} - private BuildSettings collect(GeneratorSettings settings, Package pack, ref TargetInfo[string] targets, in string[string] configs, ref string[] main_files, string bin_pack) + /** Configure `rootPackage` and all of it's dependencies. + + 1. Merge versions, debugVersions, and inheritable build + settings from dependents to their dependencies. + + 2. Define version identifiers Have_dependency_xyz for all + direct dependencies of all packages. + + 3. Merge versions, debugVersions, and inheritable build settings from + dependencies to their dependents, so that importer and importee are ABI + compatible. This also transports all Have_dependency_xyz version + identifiers to `rootPackage`. + + 4. Filter unused versions and debugVersions from all targets. The + filters have previously been upwards inherited (3.) so that versions + used in a dependency are also applied to all dependents. + + Note: The upwards inheritance is done at last so that siblings do not + influence each other, also see https://github.com/dlang/dub/pull/1128. + + Note: Targets without output are integrated into their + dependents and removed from `targets`. + */ + private void configurePackages(Package rootPackage, TargetInfo[string] targets, GeneratorSettings genSettings) { - import std.algorithm : sort; + import std.algorithm : remove, sort; + import std.range : repeat; + + // 0. do shallow configuration (not including dependencies) of all packages + TargetType determineTargetType(const ref TargetInfo ti) + { + TargetType tt = ti.buildSettings.targetType; + if (ti.pack is rootPackage) { + if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; + } else { + if (tt == TargetType.autodetect || tt == TargetType.library) tt = genSettings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; + else if (tt == TargetType.dynamicLibrary) { + logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); + tt = TargetType.staticLibrary; + } + } + if (tt != TargetType.none && tt != TargetType.sourceLibrary && ti.buildSettings.sourceFiles.empty) { + logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to its package description to avoid building it.`, + ti.config, ti.pack.name); + tt = TargetType.none; + } + return tt; + } + + string[] mainSourceFiles; + bool[string] hasOutput; + + foreach (ref ti; targets.byValue) + { + auto bs = &ti.buildSettings; + // determine the actual target type + bs.targetType = determineTargetType(ti); + + switch (bs.targetType) + { + case TargetType.none: + // ignore any build settings for targetType none (only dependencies will be processed) + *bs = BuildSettings.init; + bs.targetType = TargetType.none; + break; + + case TargetType.executable: + break; + + case TargetType.dynamicLibrary: + // set -fPIC for dynamic library builds + ti.buildSettings.addOptions(BuildOption.pic); + goto default; + + default: + // remove any mainSourceFile from non-executable builds + if (bs.mainSourceFile.length) { + bs.sourceFiles = bs.sourceFiles.remove!(f => f == bs.mainSourceFile); + mainSourceFiles ~= bs.mainSourceFile; + } + break; + } + bool generatesBinary = bs.targetType != TargetType.sourceLibrary && bs.targetType != TargetType.none; + hasOutput[ti.pack.name] = generatesBinary || ti.pack is rootPackage; + } + + // add main source files to root executable + { + auto bs = &targets[rootPackage.name].buildSettings; + if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainSourceFiles); + } + + if (genSettings.filterVersions) + foreach (ref ti; targets.byValue) + inferVersionFilters(ti); + + // mark packages as visited (only used during upwards propagation) + void[0][Package] visited; + + // collect all dependencies + void collectDependencies(Package pack, ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) + { + // use `visited` here as pkgs cannot depend on themselves + if (pack in visited) + return; + // transitive dependencies must be visited multiple times, see #1350 + immutable transitive = !hasOutput[pack.name]; + if (!transitive) + visited[pack] = typeof(visited[pack]).init; + + auto bs = &ti.buildSettings; + if (hasOutput[pack.name]) + logDebug("%sConfiguring target %s (%s %s %s)", ' '.repeat(2 * level), pack.name, bs.targetType, bs.targetPath, bs.targetName); + else + logDebug("%sConfiguring target without output %s", ' '.repeat(2 * level), pack.name); + + // get specified dependencies, e.g. vibe-d ~0.8.1 + auto deps = pack.getDependencies(targets[pack.name].config); + logDebug("deps: %s -> %(%s, %)", pack.name, deps.byKey); + foreach (depname; deps.keys.sort()) + { + auto depspec = deps[depname]; + // get selected package for that dependency, e.g. vibe-d 0.8.2-beta.2 + auto deppack = m_project.getDependency(depname, depspec.optional); + if (deppack is null) continue; // optional and not selected + + // if dependency has no output + if (!hasOutput[depname]) { + // add itself + ti.packages ~= deppack; + // and it's transitive dependencies to current target + collectDependencies(deppack, ti, targets, level + 1); + continue; + } + auto depti = &targets[depname]; + const depbs = &depti.buildSettings; + if (depbs.targetType == TargetType.executable && ti.buildSettings.targetType != TargetType.none) + continue; + + // add to (link) dependencies + ti.dependencies ~= depname; + ti.linkDependencies ~= depname; + + // recurse + collectDependencies(deppack, *depti, targets, level + 1); + + // also recursively add all link dependencies of static libraries + // preserve topological sorting of dependencies for correct link order + if (depbs.targetType == TargetType.staticLibrary) + ti.linkDependencies = ti.linkDependencies.filter!(d => !depti.linkDependencies.canFind(d)).array ~ depti.linkDependencies; + } + + enforce(!(ti.buildSettings.targetType == TargetType.none && ti.dependencies.empty), + "Package with target type \"none\" must have dependencies to build."); + } + + collectDependencies(rootPackage, targets[rootPackage.name], targets); + static if (__VERSION__ > 2070) + visited.clear(); + else + destroy(visited); + + // 1. downwards inherits versions, debugVersions, and inheritable build settings + static void configureDependencies(in ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) + { + // do not use `visited` here as dependencies must inherit + // configurations from *all* of their parents + logDebug("%sConfigure dependencies of %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); + foreach (depname; ti.dependencies) + { + auto pti = &targets[depname]; + mergeFromDependent(ti.buildSettings, pti.buildSettings); + configureDependencies(*pti, targets, level + 1); + } + } + + configureDependencies(targets[rootPackage.name], targets); + + // 2. add Have_dependency_xyz for all direct dependencies of a target + // (includes incorporated non-target dependencies and their dependencies) + foreach (ref ti; targets.byValue) + { + import std.range : chain; + import dub.internal.utils : stripDlangSpecialChars; + + auto bs = &ti.buildSettings; + auto pkgnames = ti.packages.map!(p => p.name).chain(ti.dependencies); + bs.addVersions(pkgnames.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); + } + + // 3. upwards inherit full build configurations (import paths, versions, debugVersions, versionFilters, importPaths, ...) + void configureDependents(ref TargetInfo ti, TargetInfo[string] targets, size_t level = 0) + { + // use `visited` here as pkgs cannot depend on themselves + if (ti.pack in visited) + return; + visited[ti.pack] = typeof(visited[ti.pack]).init; + + logDiagnostic("%sConfiguring dependent %s, deps:%(%s, %)", ' '.repeat(2 * level), ti.pack.name, ti.dependencies); + // embedded non-binary dependencies + foreach (deppack; ti.packages[1 .. $]) + ti.buildSettings.add(targets[deppack.name].buildSettings); + // binary dependencies + foreach (depname; ti.dependencies) + { + auto pdepti = &targets[depname]; + configureDependents(*pdepti, targets, level + 1); + mergeFromDependency(pdepti.buildSettings, ti.buildSettings); + } + } + + configureDependents(targets[rootPackage.name], targets); + static if (__VERSION__ > 2070) + visited.clear(); + else + destroy(visited); + + // 4. Filter applicable version and debug version identifiers + if (genSettings.filterVersions) + { + foreach (name, ref ti; targets) + { + import std.algorithm.sorting : partition; + + auto bs = &ti.buildSettings; + + auto filtered = bs.versions.partition!(v => bs.versionFilters.canFind(v)); + logDebug("Filtering out unused versions for %s: %s", name, filtered); + bs.versions = bs.versions[0 .. $ - filtered.length]; + + filtered = bs.debugVersions.partition!(v => bs.debugVersionFilters.canFind(v)); + logDebug("Filtering out unused debug versions for %s: %s", name, filtered); + bs.debugVersions = bs.debugVersions[0 .. $ - filtered.length]; + } + } + + // 5. override string import files in dependencies + static void overrideStringImports(ref TargetInfo ti, TargetInfo[string] targets, string[] overrides) + { + // do not use visited here as string imports can be overridden by *any* parent + // + // special support for overriding string imports in parent packages + // this is a candidate for deprecation, once an alternative approach + // has been found + if (ti.buildSettings.stringImportPaths.length) { + // override string import files (used for up to date checking) + foreach (ref f; ti.buildSettings.stringImportFiles) + { + foreach (o; overrides) + { + NativePath op; + if (f != o && NativePath(f).head == (op = NativePath(o)).head) { + logDebug("string import %s overridden by %s", f, o); + f = o; + ti.buildSettings.prependStringImportPaths(op.parentPath.toNativeString); + } + } + } + } + // add to overrides for recursion + overrides ~= ti.buildSettings.stringImportFiles; + // override dependencies + foreach (depname; ti.dependencies) + overrideStringImports(targets[depname], targets, overrides); + } + + overrideStringImports(targets[rootPackage.name], targets, null); + + // remove targets without output + foreach (name; targets.keys) + { + if (!hasOutput[name]) + targets.remove(name); + } + } + + // infer applicable version identifiers + private static void inferVersionFilters(ref TargetInfo ti) + { + import std.algorithm.searching : any; + import std.file : timeLastModified; + import std.path : extension; + import std.range : chain; + import std.regex : ctRegex, matchAll; + import std.stdio : File; + import std.datetime : Clock, SysTime, UTC; + import dub.compilers.utils : isLinkerFile; + import dub.internal.vibecompat.data.json : Json, JSONException; + + auto bs = &ti.buildSettings; + + // only infer if neither version filters are specified explicitly + if (bs.versionFilters.length || bs.debugVersionFilters.length) + { + logDebug("Using specified versionFilters for %s: %s %s", ti.pack.name, + bs.versionFilters, bs.debugVersionFilters); + return; + } + + // check all existing source files for version identifiers + static immutable dexts = [".d", ".di"]; + auto srcs = chain(bs.sourceFiles, bs.importFiles, bs.stringImportFiles) + .filter!(f => dexts.canFind(f.extension)).filter!exists; + // try to load cached filters first + auto cache = ti.pack.metadataCache; + try + { + auto cachedFilters = cache["versionFilters"]; + if (cachedFilters.type != Json.Type.undefined) + cachedFilters = cachedFilters[ti.config]; + if (cachedFilters.type != Json.Type.undefined) + { + immutable mtime = SysTime.fromISOExtString(cachedFilters["mtime"].get!string); + if (!srcs.any!(src => src.timeLastModified > mtime)) + { + auto versionFilters = cachedFilters["versions"][].map!(j => j.get!string).array; + auto debugVersionFilters = cachedFilters["debugVersions"][].map!(j => j.get!string).array; + logDebug("Using cached versionFilters for %s: %s %s", ti.pack.name, + versionFilters, debugVersionFilters); + bs.addVersionFilters(versionFilters); + bs.addDebugVersionFilters(debugVersionFilters); + return; + } + } + } + catch (JSONException e) + { + logWarn("Exception during loading invalid package cache %s.\n%s", + ti.pack.path ~ ".dub/metadata_cache.json", e); + } + + // use ctRegex for performance reasons, only small compile time increase + enum verRE = ctRegex!`(?:^|\s)version\s*\(\s*([^\s]*?)\s*\)`; + enum debVerRE = ctRegex!`(?:^|\s)debug\s*\(\s*([^\s]*?)\s*\)`; + + auto versionFilters = appender!(string[]); + auto debugVersionFilters = appender!(string[]); + + foreach (file; srcs) + { + foreach (line; File(file).byLine) + { + foreach (m; line.matchAll(verRE)) + if (!versionFilters.data.canFind(m[1])) + versionFilters.put(m[1].idup); + foreach (m; line.matchAll(debVerRE)) + if (!debugVersionFilters.data.canFind(m[1])) + debugVersionFilters.put(m[1].idup); + } + } + logDebug("Using inferred versionFilters for %s: %s %s", ti.pack.name, + versionFilters.data, debugVersionFilters.data); + bs.addVersionFilters(versionFilters.data); + bs.addDebugVersionFilters(debugVersionFilters.data); + + auto cachedFilters = cache["versionFilters"]; + if (cachedFilters.type == Json.Type.undefined) + cachedFilters = cache["versionFilters"] = [ti.config: Json.emptyObject]; + cachedFilters[ti.config] = [ + "mtime": Json(Clock.currTime(UTC()).toISOExtString), + "versions": Json(versionFilters.data.map!Json.array), + "debugVersions": Json(debugVersionFilters.data.map!Json.array), + ]; + ti.pack.metadataCache = cache; + } + + private static void mergeFromDependent(in ref BuildSettings parent, ref BuildSettings child) + { + child.addVersions(parent.versions); + child.addDebugVersions(parent.debugVersions); + child.addOptions(BuildOptions(parent.options & inheritedBuildOptions)); + } + + private static void mergeFromDependency(in ref BuildSettings child, ref BuildSettings parent) + { import dub.compilers.utils : isLinkerFile; - if (auto pt = pack.name in targets) return pt.buildSettings; - - // determine the actual target type - auto shallowbs = pack.getBuildSettings(settings.platform, configs[pack.name]); - TargetType tt = shallowbs.targetType; - if (pack is m_project.rootPackage) { - if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary; - } else { - if (tt == TargetType.autodetect || tt == TargetType.library) tt = settings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary; - else if (tt == TargetType.dynamicLibrary) { - logWarn("Dynamic libraries are not yet supported as dependencies - building as static library."); - tt = TargetType.staticLibrary; - } + parent.addDFlags(child.dflags); + parent.addVersions(child.versions); + parent.addDebugVersions(child.debugVersions); + parent.addVersionFilters(child.versionFilters); + parent.addDebugVersionFilters(child.debugVersionFilters); + parent.addImportPaths(child.importPaths); + parent.addStringImportPaths(child.stringImportPaths); + // linking of static libraries is done by parent + if (child.targetType == TargetType.staticLibrary) { + parent.addSourceFiles(child.sourceFiles.filter!isLinkerFile.array); + parent.addLibs(child.libs); + parent.addLFlags(child.lflags); } - if (tt != TargetType.none && tt != TargetType.sourceLibrary && shallowbs.sourceFiles.empty) { - logWarn(`Configuration '%s' of package %s contains no source files. Please add {"targetType": "none"} to it's package description to avoid building it.`, - configs[pack.name], pack.name); - tt = TargetType.none; - } - - shallowbs.targetType = tt; - bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; - bool is_target = generates_binary || pack is m_project.rootPackage; - - if (tt == TargetType.none) { - // ignore any build settings for targetType none (only dependencies will be processed) - shallowbs = BuildSettings.init; - shallowbs.targetType = TargetType.none; - } - - // start to build up the build settings - BuildSettings buildsettings; - processVars(buildsettings, m_project, pack, shallowbs, true); - - // remove any mainSourceFile from library builds - if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) { - buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array; - main_files ~= buildsettings.mainSourceFile; - } - - logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName); - if (is_target) - targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null); - - auto deps = pack.getDependencies(configs[pack.name]); - foreach (depname; deps.keys.sort()) { - auto depspec = deps[depname]; - auto dep = m_project.getDependency(depname, depspec.optional); - if (!dep) continue; - - auto depbs = collect(settings, dep, targets, configs, main_files, is_target ? pack.name : bin_pack); - - if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) { - // add a reference to the target binary and remove all source files in the dependency build settings - depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array; - depbs.importFiles = null; - } - - buildsettings.add(depbs); - - if (depbs.targetType == TargetType.executable) - continue; - - auto pt = (is_target ? pack.name : bin_pack) in targets; - assert(pt !is null); - if (auto pdt = depname in targets) { - pt.dependencies ~= depname; - pt.linkDependencies ~= depname; - if (depbs.targetType == TargetType.staticLibrary) - pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies; - } else pt.packages ~= dep; - } - - if (is_target) targets[pack.name].buildSettings = buildsettings.dup; - - return buildsettings; } - private string[] downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings) + // configure targets for build types such as release, or unittest-cov + private void addBuildTypeSettings(TargetInfo[string] targets, in GeneratorSettings settings) { - import dub.internal.utils : stripDlangSpecialChars; - - auto ti = &targets[target]; - ti.buildSettings.addVersions(root_settings.versions); - ti.buildSettings.addDebugVersions(root_settings.debugVersions); - ti.buildSettings.addOptions(BuildOptions(cast(BuildOptions)root_settings.options & inheritedBuildOptions)); - - // special support for overriding string imports in parent packages - // this is a candidate for deprecation, once an alternative approach - // has been found - if (ti.buildSettings.stringImportPaths.length) { - // override string import files (used for up to date checking) - foreach (ref f; ti.buildSettings.stringImportFiles) - foreach (fi; root_settings.stringImportFiles) - if (f != fi && Path(f).head == Path(fi).head) { - f = fi; - } - - // add the string import paths (used by the compiler to find the overridden files) - ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths); - } - - string[] packs = ti.packages.map!(p => p.name).array; - foreach (d; ti.dependencies) - packs ~= downwardsInheritSettings(d, targets, root_settings); - - logDebug("%s: %s", target, packs); - - // Add Have_* versions *after* downwards inheritance, so that dependencies - // are build independently of the parent packages w.r.t the other parent - // dependencies. This enables sharing of the same package build for - // multiple dependees. - ti.buildSettings.addVersions(packs.map!(pn => "Have_" ~ stripDlangSpecialChars(pn)).array); - - return packs; - } - - private void addBuildTypeSettings(TargetInfo[string] targets, GeneratorSettings settings) - { - foreach (ref t; targets) { - t.buildSettings.add(settings.buildSettings); + foreach (ref ti; targets.byValue) { + ti.buildSettings.add(settings.buildSettings); // add build type settings and convert plain DFLAGS to build options - m_project.addBuildTypeSettings(t.buildSettings, settings.platform, settings.buildType, t.pack is m_project.rootPackage); - settings.compiler.extractBuildOptions(t.buildSettings); + m_project.addBuildTypeSettings(ti.buildSettings, settings, ti.pack is m_project.rootPackage); + settings.compiler.extractBuildOptions(ti.buildSettings); - auto tt = t.buildSettings.targetType; - bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none; - enforce (generates_binary || t.pack !is m_project.rootPackage || (t.buildSettings.options & BuildOption.syntaxOnly), - format("Main package must have a binary target type, not %s. Cannot build.", tt)); + auto tt = ti.buildSettings.targetType; + enforce (tt != TargetType.sourceLibrary || ti.pack !is m_project.rootPackage || (ti.buildSettings.options & BuildOption.syntaxOnly), + format("Main package must not have target type \"%s\". Cannot build.", tt)); } } } @@ -290,11 +563,13 @@ string buildType; BuildSettings buildSettings; BuildMode buildMode = BuildMode.separate; + int targetExitStatus; bool combined; // compile all in one go instead of each dependency separately + bool filterVersions; // only used for generator "build" - bool run, force, direct, clean, rdmd, tempBuild, parallelBuild; + bool run, force, direct, rdmd, tempBuild, parallelBuild; string[] runArgs; void delegate(int status, string output) compileCallback; void delegate(int status, string output) linkCallback; @@ -360,10 +635,10 @@ Runs post-build commands and copies required files to the binary directory. */ private void finalizeGeneration(in Package pack, in Project proj, in GeneratorSettings settings, - in BuildSettings buildsettings, Path target_path, bool generate_binary) + in BuildSettings buildsettings, NativePath target_path, bool generate_binary) { import std.path : globMatch; - + if (buildsettings.postGenerateCommands.length && !isRecursiveInvocation(pack.name)) { logInfo("Running post-generate commands for %s...", pack.name); runBuildCommands(buildsettings.postGenerateCommands, pack, proj, settings, buildsettings); @@ -374,7 +649,7 @@ mkdirRecurse(buildsettings.targetPath); if (buildsettings.copyFiles.length) { - void copyFolderRec(Path folder, Path dstfolder) + void copyFolderRec(NativePath folder, NativePath dstfolder) { mkdirRecurse(dstfolder.toNativeString()); foreach (de; iterateDirectory(folder.toNativeString())) { @@ -391,9 +666,9 @@ void tryCopyDir(string file) { - auto src = Path(file); + auto src = NativePath(file); if (!src.absolute) src = pack.path ~ src; - auto dst = target_path ~ Path(file).head; + auto dst = target_path ~ NativePath(file).head; if (src == dst) { logDiagnostic("Skipping copy of %s (same source and destination)", file); return; @@ -406,9 +681,9 @@ void tryCopyFile(string file) { - auto src = Path(file); + auto src = NativePath(file); if (!src.absolute) src = pack.path ~ src; - auto dst = target_path ~ Path(file).head; + auto dst = target_path ~ NativePath(file).head; if (src == dst) { logDiagnostic("Skipping copy of %s (same source and destination)", file); return; @@ -461,7 +736,7 @@ /** Runs a list of build commands for a particular package. - This funtion sets all DUB speficic environment variables and makes sure + This function sets all DUB speficic environment variables and makes sure that recursive dub invocations are detected and don't result in infinite command execution loops. The latter could otherwise happen when a command runs "dub describe" or similar functionality. @@ -469,9 +744,9 @@ void runBuildCommands(in string[] commands, in Package pack, in Project proj, in GeneratorSettings settings, in BuildSettings build_settings) { - import std.conv; - import std.process; - import dub.internal.utils; + import dub.internal.utils : getDUBExePath, runCommands; + import std.conv : to, text; + import std.process : environment, escapeShellFileName; string[string] env = environment.toAA(); // TODO: do more elaborate things here @@ -487,12 +762,14 @@ env["DC_BASE"] = settings.platform.compiler; env["D_FRONTEND_VER"] = to!string(settings.platform.frontendVersion); + env["DUB_EXE"] = getDUBExePath(settings.platform.compilerBinary); env["DUB_PLATFORM"] = join(cast(string[])settings.platform.platform," "); env["DUB_ARCH"] = join(cast(string[])settings.platform.architecture," "); env["DUB_TARGET_TYPE"] = to!string(build_settings.targetType); env["DUB_TARGET_PATH"] = build_settings.targetPath; env["DUB_TARGET_NAME"] = build_settings.targetName; + env["DUB_TARGET_EXIT_STATUS"] = settings.targetExitStatus.text; env["DUB_WORKING_DIRECTORY"] = build_settings.workingDirectory; env["DUB_MAIN_SOURCE_FILE"] = build_settings.mainSourceFile; @@ -503,18 +780,18 @@ env["DUB_PACKAGE_DIR"] = pack.path.toNativeString(); env["DUB_ROOT_PACKAGE"] = proj.rootPackage.name; env["DUB_ROOT_PACKAGE_DIR"] = proj.rootPackage.path.toNativeString(); + env["DUB_PACKAGE_VERSION"] = pack.version_.toString(); env["DUB_COMBINED"] = settings.combined? "TRUE" : ""; env["DUB_RUN"] = settings.run? "TRUE" : ""; env["DUB_FORCE"] = settings.force? "TRUE" : ""; env["DUB_DIRECT"] = settings.direct? "TRUE" : ""; - env["DUB_CLEAN"] = settings.clean? "TRUE" : ""; env["DUB_RDMD"] = settings.rdmd? "TRUE" : ""; env["DUB_TEMP_BUILD"] = settings.tempBuild? "TRUE" : ""; env["DUB_PARALLEL_BUILD"] = settings.parallelBuild? "TRUE" : ""; env["DUB_RUN_ARGS"] = (cast(string[])settings.runArgs).map!(escapeShellFileName).join(" "); - + auto depNames = proj.dependencies.map!((a) => a.name).array(); storeRecursiveInvokations(env, proj.rootPackage.name ~ depNames); runCommands(commands, env); diff --git a/source/dub/generators/sublimetext.d b/source/dub/generators/sublimetext.d index a87ba13..9ac53c9 100644 --- a/source/dub/generators/sublimetext.d +++ b/source/dub/generators/sublimetext.d @@ -11,6 +11,7 @@ import dub.generators.generator; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; +import dub.internal.vibecompat.inet.path; import dub.packagemanager; import dub.project; @@ -34,7 +35,7 @@ { auto buildSettings = targets[m_project.name].buildSettings; logDebug("About to generate sublime project for %s.", m_project.rootPackage.name); - + auto root = Json([ "folders": targets.byValue.map!(f => targetFolderJson(f)).array.Json, "build_systems": buildSystems(settings.platform), @@ -70,6 +71,7 @@ //"plain", "debug", "release", + "release-debug", "release-nobounds", //"unittest", "docs", @@ -78,12 +80,13 @@ "profile-gc", "cov", "unittest-cov", + "syntax" ]; string fileRegex; if (buildPlatform.frontendVersion >= 2066 && buildPlatform.compiler == "dmd") - fileRegex = r"^(.+)\(([0-9]+)\,([0-9]+)\)\:() (.*)$"; + fileRegex = r"^(.+)\(([0-9]+)\,([0-9]+)\)\: (.*)$"; else fileRegex = r"^(.+)\(([0-9]+)\)\:() (.*)$"; @@ -99,7 +102,7 @@ "variants": [ [ "name": "Run".Json, - "cmd": ["dub", "run", "--build=" ~ buildType, "--arch=" ~ arch, "--compiler="~buildPlatform.compilerBinary].map!Json.array.Json, + "cmd": ["dub", "run", "--build=" ~ buildType, "--arch=" ~ arch, "--compiler="~buildPlatform.compilerBinary].map!Json.array.Json, ].Json ].array.Json, ]); diff --git a/source/dub/generators/targetdescription.d b/source/dub/generators/targetdescription.d index d2aec6e..a84883c 100644 --- a/source/dub/generators/targetdescription.d +++ b/source/dub/generators/targetdescription.d @@ -52,7 +52,7 @@ foreach (ld; ti.linkDependencies) { auto ltarget = targets[ld]; auto ltbs = ltarget.buildSettings; - auto targetfil = (Path(ltbs.targetPath) ~ settings.compiler.getTargetFileName(ltbs, settings.platform)).toNativeString(); + auto targetfil = (NativePath(ltbs.targetPath) ~ settings.compiler.getTargetFileName(ltbs, settings.platform)).toNativeString(); d.buildSettings.addLinkerFiles(targetfil); } diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index a7ace29..87c8e51 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -170,7 +170,7 @@ // Add all files auto files = targets[packname].buildSettings; SourceFile[string] sourceFiles; - void addSourceFile(Path file_path, Path structure_path, bool build) + void addSourceFile(NativePath file_path, NativePath structure_path, bool build) { auto key = file_path.toString(); auto sf = sourceFiles.get(key, SourceFile.init); @@ -183,7 +183,7 @@ } void addFile(string s, bool build) { - auto sp = Path(s); + auto sp = NativePath(s); assert(sp.absolute, format("Source path in %s expected to be absolute: %s", packname, s)); //if( !sp.absolute ) sp = pack.path ~ sp; addSourceFile(sp.relativeTo(project_file_dir), determineStructurePath(sp, targets[packname]), build); @@ -203,10 +203,10 @@ // Create folders and files ret.formattedWrite(" ", getPackageFileName(packname)); - Path lastFolder; + typeof(NativePath.init.head)[] lastFolder; foreach(source; sortedSources(sourceFiles.values)) { logDebug("source looking at %s", source.structurePath); - auto cur = source.structurePath[0 .. source.structurePath.length-1]; + auto cur = source.structurePath.parentPath.bySegment.array; if(lastFolder != cur) { size_t same = 0; foreach(idx; 0..min(lastFolder.length, cur.length)) @@ -248,7 +248,7 @@ auto ret = new string[settings.length]; foreach (i; 0 .. settings.length) { // \" is interpreted as an escaped " by cmd.exe, so we need to avoid that - auto p = Path(settings[i]).relativeTo(project_file_dir); + auto p = NativePath(settings[i]).relativeTo(project_file_dir); p.endsWithSlash = false; ret[i] = '"' ~ p.toNativeString() ~ '"'; } @@ -285,7 +285,7 @@ output_type = DynamicLib; output_ext = "dll"; } - auto bin_path = pack == m_project.rootPackage.name ? Path(buildsettings.targetPath) : Path("lib/"); + auto bin_path = pack == m_project.rootPackage.name ? NativePath(buildsettings.targetPath) : NativePath("lib/"); bin_path.endsWithSlash = true; ret.formattedWrite(" %s\n", output_type); ret.formattedWrite(" %s%s.%s\n", bin_path.toNativeString(), buildsettings.targetName, output_ext); @@ -315,10 +315,10 @@ // compute directory for intermediate files (need dummy/ because of how -op determines the resulting path) size_t ndummy = 0; foreach (f; buildsettings.sourceFiles) { - auto rpath = Path(f).relativeTo(project_file_dir); + auto rpath = NativePath(f).relativeTo(project_file_dir); size_t nd = 0; - foreach (i; 0 .. rpath.length) - if (rpath[i] == "..") + foreach (s; rpath.bySegment) + if (s == "..") nd++; if (nd > ndummy) ndummy = nd; } @@ -336,6 +336,7 @@ //case compileOnly: singlefilemode = 3; break; } ret.formattedWrite(" %s\n", singlefilemode); + ret.formattedWrite(" %s", buildsettings.dflags.canFind("-m32mscoff") ? "1" : "0"); ret.put(" 0\n"); ret.put(" 0\n"); ret.put(" 0\n"); @@ -348,6 +349,7 @@ ret.put(" 0\n"); ret.put(" 0\n"); ret.put(" 0\n"); + ret.put(" 0\n"); ret.put(" 0\n"); ret.put(" 0\n"); ret.put(" 0\n"); @@ -405,7 +407,7 @@ ret.put(" \n"); ret.put(" \n"); ret.put(" \n"); - auto wdir = Path(buildsettings.workingDirectory); + auto wdir = NativePath(buildsettings.workingDirectory); if (!wdir.absolute) wdir = m_project.rootPackage.path ~ wdir; ret.formattedWrite(" %s\n", wdir.relativeTo(project_file_dir).toNativeString()); @@ -440,8 +442,8 @@ else return getPackageFileName(m_project.rootPackage.name) ~ ".sln"; } - Path projFileName(string pack) const { - auto basepath = Path(".dub/"); + NativePath projFileName(string pack) const { + auto basepath = NativePath(".dub/"); version(DUBBING) return basepath ~ (getPackageFileName(pack) ~ ".dubbed.visualdproj"); else return basepath ~ (getPackageFileName(pack) ~ ".visualdproj"); } @@ -449,8 +451,8 @@ // TODO: nice folders private struct SourceFile { - Path structurePath; - Path filePath; + NativePath structurePath; + NativePath filePath; bool build; hash_t toHash() const nothrow @trusted { return structurePath.toHash() ^ filePath.toHash() ^ (build * 0x1f3e7b2c); } @@ -459,13 +461,18 @@ private final static int sortOrder(ref const SourceFile a, ref const SourceFile b) { assert(!a.structurePath.empty); assert(!b.structurePath.empty); - auto as = a.structurePath; - auto bs = b.structurePath; + static if (is(typeof(a.structurePath.nodes))) { // vibe.d < 0.8.2 + auto as = a.structurePath.nodes; + auto bs = b.structurePath.nodes; + } else { + auto as = a.structurePath.bySegment.array; + auto bs = b.structurePath.bySegment.array; + } // Check for different folders, compare folders only (omit last one). for(uint idx=0; idx0.7.28"); + p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.8.2"); p.description = "A simple vibe.d server application."; - p.buildSettings.versions[""] ~= "VibeDefaultMain"; pre_write_callback(); createDirectory(root_path ~ "source"); createDirectory(root_path ~ "views"); createDirectory(root_path ~ "public"); write((root_path ~ "source/app.d").toNativeString(), -q{import vibe.d; +q{import vibe.vibe; -shared static this() +void main() { auto settings = new HTTPServerSettings; settings.port = 8080; @@ -132,6 +131,7 @@ listenHTTP(settings, &hello); logInfo("Please open http://127.0.0.1:8080/ in your browser."); + runApplication(); } void hello(HTTPServerRequest req, HTTPServerResponse res) @@ -141,7 +141,7 @@ }); } -private void initDeimosPackage(Path root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) +private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) { import dub.compilers.buildsettings : TargetType; @@ -155,10 +155,45 @@ createDirectory(root_path ~ "deimos"); } -private void writeGitignore(Path root_path) +/** + * Write the `.gitignore` file to the directory, if it does not already exists + * + * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for + * most users. However, this file is not mandatory for `dub` to do its job, + * so we do not depend on the content. + * One important use case we need to support is people running `dub init` on + * a Github-initialized repository. Those might already contain a `.gitignore` + * (and a README and a LICENSE), thus we should not bail out if the file already + * exists, just ignore it. + * + * Params: + * root_path = The path to the directory hosting the project + * pkg_name = Name of the package, to generate a list of binaries to ignore + */ +private void writeGitignore(NativePath root_path, const(char)[] pkg_name) { - write((root_path ~ ".gitignore").toNativeString(), - ".dub\ndocs.json\n__dummy.html\n*.o\n*.obj\n__test__*__\n"); + auto full_path = (root_path ~ ".gitignore").toNativeString(); + + if (existsFile(full_path)) + return; + + write(full_path, +q"{.dub +docs.json +__dummy.html +docs/ +/%1$s +%1$s.so +%1$s.dylib +%1$s.dll +%1$s.a +%1$s.lib +%1$s-test-* +*.exe +*.o +*.obj +*.lst +}".format(pkg_name)); } private string getUserName() @@ -170,6 +205,8 @@ import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen; import std.algorithm : splitter; + // Bionic doesn't have pw_gecos on ARM + version(CRuntime_Bionic) {} else if (auto pw = getpwuid(getuid)) { auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); diff --git a/source/dub/internal/libInputVisitor.d b/source/dub/internal/libInputVisitor.d index ba091b0..d388b78 100644 --- a/source/dub/internal/libInputVisitor.d +++ b/source/dub/internal/libInputVisitor.d @@ -12,17 +12,17 @@ To Public License, Version 2, as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 -Copyright (C) 2004 Sam Hocevar +Copyright (C) 2004 Sam Hocevar -Everyone is permitted to copy and distribute verbatim or modified -copies of this license document, and changing it is allowed as long -as the name is changed. +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. +/ @@ -50,7 +50,7 @@ { obj.visit(this); } - + private void ensureStarted() { if(!started) @@ -59,7 +59,7 @@ started = true; } } - + // Member 'front' must be a function due to DMD Issue #5403 private Elem _front; @property Elem front() @@ -67,19 +67,19 @@ ensureStarted(); return _front; } - + void popFront() { ensureStarted(); call(); } - + @property bool empty() { ensureStarted(); return state == Fiber.State.TERM; } - + void yield(Elem elem) { _front = elem; diff --git a/source/dub/internal/sdlang/ast.d b/source/dub/internal/sdlang/ast.d index cf4f0e0..deda488 100644 --- a/source/dub/internal/sdlang/ast.d +++ b/source/dub/internal/sdlang/ast.d @@ -27,7 +27,7 @@ { Value value; Location location; - + private Tag _parent; /// Get parent tag. To set a parent, attach this Attribute to its intended /// parent tag by calling 'Tag.add(...)', or by passing it to @@ -62,7 +62,7 @@ else _namespace = value; } - + private string _name; /// Not including namespace. Use 'fullName' if you want the namespace included. @property string name() @@ -75,7 +75,7 @@ if(_parent && _name != value) { _parent.updateId++; - + void removeFromGroupedLookup(string ns) { // Remove from _parent._attributes[ns] @@ -83,14 +83,14 @@ auto targetIndex = sameNameAttrs.countUntil(this); _parent._attributes[ns][_name].removeIndex(targetIndex); } - + // Remove from _parent._tags removeFromGroupedLookup(_namespace); removeFromGroupedLookup("*"); // Change name _name = value; - + // Add to new locations in _parent._attributes _parent._attributes[_namespace][_name] ~= this; _parent._attributes["*"][_name] ~= this; @@ -111,7 +111,7 @@ this.location = location; this.value = value; } - + this(string name, Value value, Location location = Location(0, 0, 0)) { this._namespace = ""; @@ -119,14 +119,14 @@ this.location = location; this.value = value; } - + /// Removes 'this' from its parent, if any. Returns 'this' for chaining. /// Inefficient ATM, but it works. Attribute remove() { if(!_parent) return this; - + void removeFromGroupedLookup(string ns) { // Remove from _parent._attributes[ns] @@ -134,7 +134,7 @@ auto targetIndex = sameNameAttrs.countUntil(this); _parent._attributes[ns][_name].removeIndex(targetIndex); } - + // Remove from _parent._attributes removeFromGroupedLookup(_namespace); removeFromGroupedLookup("*"); @@ -147,13 +147,13 @@ auto sameNamespaceAttrs = _parent.attributeIndicies[_namespace]; auto attrIndiciesIndex = sameNamespaceAttrs.countUntil(allAttrsIndex); _parent.attributeIndicies[_namespace].removeIndex(attrIndiciesIndex); - + // Fixup other indicies foreach(ns, ref nsAttrIndicies; _parent.attributeIndicies) foreach(k, ref v; nsAttrIndicies) if(v > allAttrsIndex) v--; - + _parent.removeNamespaceIfEmpty(_namespace); _parent.updateId++; _parent = null; @@ -171,7 +171,7 @@ _name == a._name && value == a.value; } - + string toSDLString()() { Appender!string sink; @@ -232,7 +232,7 @@ else _namespace = value; } - + private string _name; /// Not including namespace. Use 'fullName' if you want the namespace included. @property string name() @@ -245,7 +245,7 @@ if(_parent && _name != value) { _parent.updateId++; - + void removeFromGroupedLookup(string ns) { // Remove from _parent._tags[ns] @@ -253,14 +253,14 @@ auto targetIndex = sameNameTags.countUntil(this); _parent._tags[ns][_name].removeIndex(targetIndex); } - + // Remove from _parent._tags removeFromGroupedLookup(_namespace); removeFromGroupedLookup("*"); - + // Change name _name = value; - + // Add to new locations in _parent._tags _parent._tags[_namespace][_name] ~= this; _parent._tags["*"][_name] ~= this; @@ -268,7 +268,7 @@ else _name = value; } - + /// This tag's name, including namespace if one exists. @property string fullName() { @@ -279,7 +279,7 @@ // could invalidate existing ranges. This way, the ranges can detect when // they've been invalidated. private size_t updateId=0; - + this(Tag parent = null) { if(parent) @@ -304,7 +304,7 @@ if(parent) parent.add(this); - + this.values = values; this.add(attributes); this.add(children); @@ -319,7 +319,7 @@ private Attribute[][string][string] _attributes; // attributes[namespace or "*"][name][i] private Tag[][string][string] _tags; // tags[namespace or "*"][name][i] - + /// Adds a Value, Attribute, Tag (or array of such) as a member/child of this Tag. /// Returns 'this' for chaining. /// Throws 'SDLangValidationException' if trying to add an Attribute or Tag @@ -330,7 +330,7 @@ updateId++; return this; } - + ///ditto Tag add(Value[] vals) { @@ -339,7 +339,7 @@ return this; } - + ///ditto Tag add(Attribute attr) { @@ -350,12 +350,12 @@ "Use Attribute.remove() before adding it to another tag." ); } - + if(!allNamespaces.canFind(attr._namespace)) allNamespaces ~= attr._namespace; attr._parent = this; - + allAttributes ~= attr; attributeIndicies[attr._namespace] ~= allAttributes.length-1; _attributes[attr._namespace][attr._name] ~= attr; @@ -364,7 +364,7 @@ updateId++; return this; } - + ///ditto Tag add(Attribute[] attrs) { @@ -373,7 +373,7 @@ return this; } - + ///ditto Tag add(Tag tag) { @@ -387,18 +387,18 @@ if(!allNamespaces.canFind(tag._namespace)) allNamespaces ~= tag._namespace; - + tag._parent = this; allTags ~= tag; tagIndicies[tag._namespace] ~= allTags.length-1; _tags[tag._namespace][tag._name] ~= tag; _tags["*"] [tag._name] ~= tag; - + updateId++; return this; } - + ///ditto Tag add(Tag[] tags) { @@ -407,14 +407,14 @@ return this; } - + /// Removes 'this' from its parent, if any. Returns 'this' for chaining. /// Inefficient ATM, but it works. Tag remove() { if(!_parent) return this; - + void removeFromGroupedLookup(string ns) { // Remove from _parent._tags[ns] @@ -422,7 +422,7 @@ auto targetIndex = sameNameTags.countUntil(this); _parent._tags[ns][_name].removeIndex(targetIndex); } - + // Remove from _parent._tags removeFromGroupedLookup(_namespace); removeFromGroupedLookup("*"); @@ -435,19 +435,19 @@ auto sameNamespaceTags = _parent.tagIndicies[_namespace]; auto tagIndiciesIndex = sameNamespaceTags.countUntil(allTagsIndex); _parent.tagIndicies[_namespace].removeIndex(tagIndiciesIndex); - + // Fixup other indicies foreach(ns, ref nsTagIndicies; _parent.tagIndicies) foreach(k, ref v; nsTagIndicies) if(v > allTagsIndex) v--; - + _parent.removeNamespaceIfEmpty(_namespace); _parent.updateId++; _parent = null; return this; } - + private void removeNamespaceIfEmpty(string namespace) { // If namespace has no attributes, remove it from attributeIndicies/_attributes @@ -463,7 +463,7 @@ tagIndicies.remove(namespace); _tags.remove(namespace); } - + // If namespace is now empty, remove it from allNamespaces if( namespace !in tagIndicies && @@ -474,7 +474,7 @@ allNamespaces = allNamespaces[0..allNamespacesIndex] ~ allNamespaces[allNamespacesIndex+1..$]; } } - + struct NamedMemberRange(T, string membersGrouped) { private Tag tag; @@ -498,7 +498,7 @@ else endIndex = 0; } - + invariant() { assert( @@ -511,7 +511,7 @@ { return frontIndex == endIndex; } - + private size_t frontIndex; @property T front() { @@ -537,13 +537,13 @@ endIndex--; } - + alias length opDollar; @property size_t length() { return endIndex - frontIndex; } - + @property typeof(this) save() { auto r = typeof(this)(this.tag, this.namespace, this.name, this.updateId); @@ -551,25 +551,25 @@ r.endIndex = this.endIndex; return r; } - + typeof(this) opSlice() { return save(); } - + typeof(this) opSlice(size_t start, size_t end) { auto r = save(); r.frontIndex = this.frontIndex + start; r.endIndex = this.frontIndex + end; - + if( r.frontIndex > this.endIndex || r.endIndex > this.endIndex || r.frontIndex > r.endIndex ) throw new SDLangRangeException("Slice out of range"); - + return r; } @@ -604,10 +604,10 @@ initialEndIndex = mixin("tag."~memberIndicies~"[namespace].length"); else initialEndIndex = 0; - + endIndex = initialEndIndex; } - + invariant() { assert( @@ -620,7 +620,7 @@ { return frontIndex == endIndex; } - + private size_t frontIndex; @property T front() { @@ -646,13 +646,13 @@ endIndex--; } - + alias length opDollar; @property size_t length() { return endIndex - frontIndex; } - + @property typeof(this) save() { auto r = typeof(this)(this.tag, this.namespace, this.isMaybe); @@ -662,28 +662,28 @@ r.updateId = this.updateId; return r; } - + typeof(this) opSlice() { return save(); } - + typeof(this) opSlice(size_t start, size_t end) { auto r = save(); r.frontIndex = this.frontIndex + start; r.endIndex = this.frontIndex + end; - + if( r.frontIndex > this.endIndex || r.endIndex > this.endIndex || r.frontIndex > r.endIndex ) throw new SDLangRangeException("Slice out of range"); - + return r; } - + T opIndex(size_t index) { if(empty) @@ -694,7 +694,7 @@ else return mixin("tag."~allMembers~"[ tag."~memberIndicies~"[namespace][frontIndex+index] ]"); } - + alias NamedMemberRange!(T,membersGrouped) ThisNamedMemberRange; ThisNamedMemberRange opIndex(string name) { @@ -707,10 +707,10 @@ "range and that you aren't using a slice of the range." ); } - + if(!isMaybe && empty) throw new SDLangRangeException("Range is empty"); - + if(!isMaybe && name !in this) throw new SDLangRangeException(`No such `~T.stringof~` named: "`~name~`"`); @@ -728,10 +728,10 @@ "range and that you aren't using a slice of the range." ); } - + return namespace in mixin("tag."~membersGrouped) && - name in mixin("tag."~membersGrouped~"[namespace]") && + name in mixin("tag."~membersGrouped~"[namespace]") && mixin("tag."~membersGrouped~"[namespace][name].length") > 0; } } @@ -758,12 +758,12 @@ "This range has been invalidated by a change to the tag." ); } - + @property bool empty() { return frontIndex == endIndex; } - + private size_t frontIndex; @property NamespaceAccess front() { @@ -773,7 +773,7 @@ { if(empty) throw new SDLangRangeException("Range is empty"); - + frontIndex++; } @@ -786,16 +786,16 @@ { if(empty) throw new SDLangRangeException("Range is empty"); - + endIndex--; } - + alias length opDollar; @property size_t length() { return endIndex - frontIndex; } - + @property NamespaceRange save() { auto r = NamespaceRange(this.tag, this.isMaybe); @@ -804,28 +804,28 @@ r.updateId = this.updateId; return r; } - + typeof(this) opSlice() { return save(); } - + typeof(this) opSlice(size_t start, size_t end) { auto r = save(); r.frontIndex = this.frontIndex + start; r.endIndex = this.frontIndex + end; - + if( r.frontIndex > this.endIndex || r.endIndex > this.endIndex || r.frontIndex > r.endIndex ) throw new SDLangRangeException("Slice out of range"); - + return r; } - + NamespaceAccess opIndex(size_t index) { if(empty) @@ -838,22 +838,22 @@ TagRange(tag, namespace, isMaybe) ); } - + NamespaceAccess opIndex(string namespace) { if(!isMaybe && empty) throw new SDLangRangeException("Range is empty"); - + if(!isMaybe && namespace !in this) throw new SDLangRangeException(`No such namespace: "`~namespace~`"`); - + return NamespaceAccess( namespace, AttributeRange(tag, namespace, isMaybe), TagRange(tag, namespace, isMaybe) ); } - + /// Inefficient when range is a slice or has used popFront/popBack, but it works. bool opBinaryRight(string op)(string namespace) if(op=="in") { @@ -893,7 +893,7 @@ { return TagRange(this, "", false); } - + /// Access all namespaces in this tag, and the attributes/tags within them. @property NamespaceRange namespaces() { @@ -926,7 +926,7 @@ { return TagRange(tag, "", true); } - + /// Access all namespaces in this tag, and the attributes/tags within them. @property NamespaceRange namespaces() { @@ -944,21 +944,21 @@ ); } } - + /// Access 'attributes', 'tags', 'namespaces' and 'all' like normal, - /// except that looking up a non-existant name/namespace with + /// except that looking up a non-existent name/namespace with /// opIndex(string) results in an empty array instead of a thrown SDLangRangeException. @property MaybeAccess maybe() { return MaybeAccess(this); } - + override bool opEquals(Object o) { auto t = cast(Tag)o; if(!t) return false; - + if(_namespace != t._namespace || _name != t._name) return false; @@ -969,7 +969,7 @@ allTags .length != t.allTags .length ) return false; - + if(values != t.values) return false; @@ -978,12 +978,12 @@ if(allAttributes != t.allAttributes) return false; - + // Ok because cycles are not allowed //TODO: Actually check for or prevent cycles. return allTags == t.allTags; } - + /// Treats 'this' as the root tag. Note that root tags cannot have /// values or attributes, and cannot be part of a namespace. /// If this isn't a valid root tag, 'SDLangValidationException' will be thrown. @@ -993,7 +993,7 @@ toSDLDocument(sink, indent, indentLevel); return sink.data; } - + ///ditto void toSDLDocument(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) if(isOutputRange!(Sink,char)) @@ -1006,11 +1006,11 @@ if(_namespace != "") throw new SDLangValidationException("Root tags cannot have a namespace."); - + foreach(tag; allTags) tag.toSDLString(sink, indent, indentLevel); } - + /// Output this entire tag in SDL format. Does *not* treat 'this' as /// a root tag. If you intend this to be the root of a standard SDL /// document, use 'toSDLDocument' instead. @@ -1020,21 +1020,21 @@ toSDLString(sink, indent, indentLevel); return sink.data; } - + ///ditto void toSDLString(Sink)(ref Sink sink, string indent="\t", int indentLevel=0) if(isOutputRange!(Sink,char)) { if(_name == "" && values.length == 0) throw new SDLangValidationException("Anonymous tags must have at least one value."); - + if(_name == "" && _namespace != "") throw new SDLangValidationException("Anonymous tags cannot have a namespace."); - + // Indent foreach(i; 0..indentLevel) sink.put(indent); - + // Name if(_namespace != "") { @@ -1042,24 +1042,24 @@ sink.put(':'); } sink.put(_name); - + // Values foreach(i, v; values) { // Omit the first space for anonymous tags if(_name != "" || i > 0) sink.put(' '); - + v.toSDLString(sink); } - + // Attributes foreach(attr; allAttributes) { sink.put(' '); attr.toSDLString(sink); } - + // Child tags bool foundChild=false; foreach(tag; allTags) @@ -1089,7 +1089,7 @@ import std.algorithm : sort; Appender!string buf; - + buf.put("\n"); buf.put("Tag "); if(_namespace != "") @@ -1113,21 +1113,21 @@ string namespaceStr; if(attr._namespace != "") namespaceStr = "["~attr._namespace~"]"; - + buf.put( " %s%s(%s): %s\n".format( namespaceStr, attr._name, .toString(attr.value.type), attr.value ) ); } - + // Children foreach(tagNamespace; _tags.keys.sort()) if(tagNamespace != "*") foreach(tagName; _tags[tagNamespace].keys.sort()) foreach(tag; _tags[tagNamespace][tagName]) buf.put( tag.toDebugString().replace("\n", "\n ") ); - + return buf.data; } } @@ -1147,14 +1147,14 @@ assert(range.empty); return; } - + static bool defaultEquals(E e1, E e2) { return e1 == e2; } if(equals is null) equals = &defaultEquals; - + assert(equals(range.front, expected[0])); assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' assert(equals(range.front, expected[0])); // Ensure consistent result from '.front' @@ -1162,20 +1162,20 @@ assert(equals(range.back, expected[$-1])); assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' assert(equals(range.back, expected[$-1])); // Ensure consistent result from '.back' - + // Forward iteration auto original = range.save; auto r2 = range.save; foreach(i; 0..expected.length) { //trace("Forward iteration: ", i); - + // Test length/empty assert(range.length == expected.length - i); assert(range.length == r2.length); assert(!range.empty); assert(!r2.empty); - + // Test front assert(equals(range.front, expected[i])); assert(equals(range.front, r2.front)); @@ -1201,7 +1201,7 @@ assert(range.empty); assert(r2.empty); assert(original.length == expected.length); - + // Backwards iteration range = original.save; r2 = original.save; @@ -1214,7 +1214,7 @@ assert(range.length == r2.length); assert(!range.empty); assert(!r2.empty); - + // Test front assert(equals(range.front, expected[0])); assert(equals(range.front, r2.front)); @@ -1240,7 +1240,7 @@ assert(range.empty); assert(r2.empty); assert(original.length == expected.length); - + // Random access range = original.save; r2 = original.save; @@ -1253,7 +1253,7 @@ assert(range.length == r2.length); assert(!range.empty); assert(!r2.empty); - + // Test front assert(equals(range.front, expected[0])); assert(equals(range.front, r2.front)); @@ -1278,13 +1278,13 @@ import sdlang.parser; writeln("Unittesting sdlang ast..."); stdout.flush(); - + Tag root; root = parseSource(""); testRandomAccessRange(root.attributes, cast( Attribute[])[]); testRandomAccessRange(root.tags, cast( Tag[])[]); testRandomAccessRange(root.namespaces, cast(Tag.NamespaceAccess[])[]); - + root = parseSource(` blue 3 "Lee" isThree=true blue 5 "Chan" 12345 isThree=false @@ -1293,7 +1293,7 @@ stuff:triangle data:points=3 data:dimensions=2 nothing namespaces small:A=1 med:A=2 big:A=3 small:B=10 big:B=30 - + people visitor:a=1 b=2 { chiyo "Small" "Flies?" nemesis="Car" score=100 yukari @@ -1407,7 +1407,7 @@ ], [chiyo, yukari, sana, tomo, hayama] ); - + assert(blue3 .opEquals( blue3 )); assert(blue5 .opEquals( blue5 )); assert(orange .opEquals( orange )); @@ -1421,19 +1421,19 @@ assert(sana .opEquals( sana )); assert(tomo .opEquals( tomo )); assert(hayama .opEquals( hayama )); - + assert(!blue3.opEquals(orange)); assert(!blue3.opEquals(people)); assert(!blue3.opEquals(sana)); assert(!blue3.opEquals(blue5)); assert(!blue5.opEquals(blue3)); - + alias Tag.NamespaceAccess NSA; static bool namespaceEquals(NSA n1, NSA n2) { return n1.name == n2.name; } - + testRandomAccessRange(root.attributes, cast(Attribute[])[]); testRandomAccessRange(root.tags, [blue3, blue5, nothing, namespaces, people]); testRandomAccessRange(root.namespaces, [NSA(""), NSA("stuff")], &namespaceEquals); @@ -1480,7 +1480,7 @@ assertThrown!SDLangRangeException(root.all.tags["foobar"]); assertThrown!SDLangRangeException(root.attributes["foobar"]); assertThrown!SDLangRangeException(root.all.attributes["foobar"]); - + // DMD Issue #12585 causes a segfault in these two tests when using 2.064 or 2.065, // so work around it. //assertThrown!SDLangRangeException(root.namespaces["foobar"].tags["foobar"]); @@ -1491,7 +1491,7 @@ catch(SDLangRangeException e) didCatch = true; assert(didCatch); - + didCatch = false; try auto x = root.namespaces["foobar"].attributes["foobar"]; @@ -1520,19 +1520,19 @@ testRandomAccessRange(blue3.namespaces, [NSA("")], &namespaceEquals); testRandomAccessRange(blue3.all.attributes, [ new Attribute("isThree", Value(true)) ]); testRandomAccessRange(blue3.all.tags, cast(Tag[])[]); - + testRandomAccessRange(blue5.attributes, [ new Attribute("isThree", Value(false)) ]); testRandomAccessRange(blue5.tags, cast(Tag[])[]); testRandomAccessRange(blue5.namespaces, [NSA("")], &namespaceEquals); testRandomAccessRange(blue5.all.attributes, [ new Attribute("isThree", Value(false)) ]); testRandomAccessRange(blue5.all.tags, cast(Tag[])[]); - + testRandomAccessRange(orange.attributes, cast(Attribute[])[]); testRandomAccessRange(orange.tags, cast(Tag[])[]); testRandomAccessRange(orange.namespaces, cast(NSA[])[], &namespaceEquals); testRandomAccessRange(orange.all.attributes, cast(Attribute[])[]); testRandomAccessRange(orange.all.tags, cast(Tag[])[]); - + testRandomAccessRange(square.attributes, [ new Attribute("points", Value(4)), new Attribute("dimensions", Value(2)), @@ -1546,7 +1546,7 @@ new Attribute("points", Value("Still four")), ]); testRandomAccessRange(square.all.tags, cast(Tag[])[]); - + testRandomAccessRange(triangle.attributes, cast(Attribute[])[]); testRandomAccessRange(triangle.tags, cast(Tag[])[]); testRandomAccessRange(triangle.namespaces, [NSA("data")], &namespaceEquals); @@ -1565,13 +1565,13 @@ new Attribute("data", "dimensions", Value(2)), ]); testRandomAccessRange(triangle.all.tags, cast(Tag[])[]); - + testRandomAccessRange(nothing.attributes, cast(Attribute[])[]); testRandomAccessRange(nothing.tags, cast(Tag[])[]); testRandomAccessRange(nothing.namespaces, cast(NSA[])[], &namespaceEquals); testRandomAccessRange(nothing.all.attributes, cast(Attribute[])[]); testRandomAccessRange(nothing.all.tags, cast(Tag[])[]); - + testRandomAccessRange(namespaces.attributes, cast(Attribute[])[]); testRandomAccessRange(namespaces.tags, cast(Tag[])[]); testRandomAccessRange(namespaces.namespaces, [NSA("small"), NSA("med"), NSA("big")], &namespaceEquals); @@ -1680,19 +1680,19 @@ new Attribute("score", Value(100)), ]); testRandomAccessRange(chiyo.all.tags, cast(Tag[])[]); - + testRandomAccessRange(yukari.attributes, cast(Attribute[])[]); testRandomAccessRange(yukari.tags, cast(Tag[])[]); testRandomAccessRange(yukari.namespaces, cast(NSA[])[], &namespaceEquals); testRandomAccessRange(yukari.all.attributes, cast(Attribute[])[]); testRandomAccessRange(yukari.all.tags, cast(Tag[])[]); - + testRandomAccessRange(sana.attributes, cast(Attribute[])[]); testRandomAccessRange(sana.tags, cast(Tag[])[]); testRandomAccessRange(sana.namespaces, cast(NSA[])[], &namespaceEquals); testRandomAccessRange(sana.all.attributes, cast(Attribute[])[]); testRandomAccessRange(sana.all.tags, cast(Tag[])[]); - + testRandomAccessRange(people.attributes, [new Attribute("b", Value(2))]); testRandomAccessRange(people.tags, [chiyo, yukari, tomo]); testRandomAccessRange(people.namespaces, [NSA("visitor"), NSA("")], &namespaceEquals); @@ -1712,7 +1712,7 @@ new Attribute("b", Value(2)), ]); testRandomAccessRange(people.all.tags, [chiyo, yukari, sana, tomo, hayama]); - + people.attributes["b"][0].name = "b_"; people.namespaces["visitor"].attributes["a"][0].name = "a_"; people.tags["chiyo"][0].name = "chiyo_"; @@ -1751,7 +1751,7 @@ testRandomAccessRange(people.namespaces["visitor"].tags, [sana_]); testRandomAccessRange(people.namespaces[ ""].tags, [yukari]); testRandomAccessRange(people.all.tags, [yukari, sana_]); - + people.attributes["b_"][0].namespace = "_"; people.namespaces["visitor"].attributes["a_"][0].namespace = "visitor_"; assert("_" in people.namespaces); @@ -1767,7 +1767,7 @@ assert(people.namespaces["_" ].attributes["b_"][0] == new Attribute("_", "b_", Value(2))); assert(people.namespaces["visitor_"].attributes["a_"][0] == new Attribute("visitor_", "a_", Value(1))); assert(people.namespaces["visitor_"].tags["sana_"][0] == sanaVisitor_); - + people.tags["yukari"][0].remove(); people.namespaces["visitor_"].tags["sana_"][0].remove(); people.namespaces["visitor_"].attributes["a_"][0].namespace = "visitor"; @@ -1782,7 +1782,7 @@ testRandomAccessRange(people.namespaces["visitor"].tags, cast(Tag[])[]); testRandomAccessRange(people.namespaces[ ""].tags, cast(Tag[])[]); testRandomAccessRange(people.all.tags, cast(Tag[])[]); - + people.namespaces["visitor"].attributes["a_"][0].remove(); testRandomAccessRange(people.attributes, [new Attribute("b_", Value(2))]); testRandomAccessRange(people.namespaces, [NSA("")], &namespaceEquals); @@ -1794,7 +1794,7 @@ testRandomAccessRange(people.all.attributes, [ new Attribute("b_", Value(2)), ]); - + people.attributes["b_"][0].remove(); testRandomAccessRange(people.attributes, cast(Attribute[])[]); testRandomAccessRange(people.namespaces, cast(NSA[])[], &namespaceEquals); @@ -1811,7 +1811,7 @@ import sdlang.parser; writeln("ast: Regression test issue #11..."); stdout.flush(); - + auto root = parseSource( `// a`); @@ -1829,7 +1829,7 @@ null, "", "child", null, null, null ); - + assert("parent" in root.tags); assert("child" !in root.tags); testRandomAccessRange(root.tags["parent"][0].tags, [child]); diff --git a/source/dub/internal/sdlang/lexer.d b/source/dub/internal/sdlang/lexer.d index 4cc754b..dd99719 100644 --- a/source/dub/internal/sdlang/lexer.d +++ b/source/dub/internal/sdlang/lexer.d @@ -1,4 +1,4 @@ -// SDLang-D +// SDLang-D // Written in the D programming language. module dub.internal.sdlang.lexer; @@ -35,7 +35,7 @@ Token[] lexSource(string source, string filename=null) { auto lexer = scoped!Lexer(source, filename); - + // Can't use 'std.array.array(Range)' because 'lexer' is scoped // and therefore cannot have its reference copied. Appender!(Token[]) tokens; @@ -98,18 +98,18 @@ private size_t posAfterLookahead; // Position after lookahead character (an index into source) private Location tokenStart; // The starting location of the token being lexed - + // Length so far of the token being lexed, not including current char private size_t tokenLength; // Length in UTF-8 code units private size_t tokenLength32; // Length in UTF-32 code units - + // Slight kludge: // If a numeric fragment is found after a Date (separated by arbitrary // whitespace), it could be the "hours" part of a DateTime, or it could // be a separate numeric literal that simply follows a plain Date. If the // latter, then the Date must be emitted, but numeric fragment that was // found after it needs to be saved for the the lexer's next iteration. - // + // // It's a slight kludge, and could instead be implemented as a slightly // kludgey parser hack, but it's the only situation where SDL's lexing // needs to lookahead more than one character, so this is good enough. @@ -121,12 +121,12 @@ Location tokenStart; } private LookaheadTokenInfo lookaheadTokenInfo; - + this(string source=null, string filename=null) { this.filename = filename; this.source = source; - + _front = Token(symbol!"Error", Location()); lookaheadTokenInfo = LookaheadTokenInfo.init; @@ -135,14 +135,14 @@ source = source[ ByteOrderMarks[BOM.UTF8].length .. $ ]; this.source = source; } - + foreach(bom; ByteOrderMarks) if( source.startsWith(bom) ) error(Location(filename,0,0,0), "SDL spec only supports UTF-8, not UTF-16 or UTF-32"); - + if(source == "") mixin(accept!"EOF"); - + // Prime everything hasNextCh = true; nextCh = source.decode(posAfterLookahead); @@ -150,12 +150,12 @@ location = Location(filename, 0, 0, 0); popFront(); } - + @property bool empty() { return _front.symbol == symbol!"EOF"; } - + Token _front; @property Token front() { @@ -183,12 +183,12 @@ tok.data = tokenData; return tok; } - + private @property string tokenData() { return source[ tokenStart.index .. location.index ]; } - + /// Check the lookahead character private bool lookahead(dchar ch) { @@ -228,20 +228,20 @@ if(ch >= '0' && ch <= '9') return true; - + return ch == '+' || ch == '/' || ch == '='; } - + /// Is the current character one that's allowed /// immediately *after* an int/float literal? private bool isEndOfNumber() { if(isEOF) return true; - + return !isDigit(ch) && ch != ':' && ch != '_' && !isAlpha(ch); } - + /// Is current character the last one in an ident? private bool isEndOfIdentCached = false; private bool _isEndOfIdent; @@ -253,10 +253,10 @@ _isEndOfIdent = true; else _isEndOfIdent = !isIdentChar(nextCh); - + isEndOfIdentCached = true; } - + return _isEndOfIdent; } @@ -265,12 +265,12 @@ { if(isAlpha(ch)) return true; - + else if(isNumber(ch)) return true; - + else - return + return ch == '-' || ch == '_' || ch == '.' || @@ -281,7 +281,7 @@ { return ch >= '0' && ch <= '9'; } - + private enum KeywordResult { Accept, // Keyword is matched @@ -350,7 +350,7 @@ hasNextCh = false; return; } - + nextCh = source.decode(posAfterLookahead); isEndOfIdentCached = false; } @@ -370,12 +370,12 @@ if(isEOF) mixin(accept!"EOF"); - + tokenStart = location; tokenLength = 0; tokenLength32 = 0; isEndOfIdentCached = false; - + if(lookaheadTokenInfo.exists) { tokenStart = lookaheadTokenInfo.tokenStart; @@ -385,31 +385,31 @@ lexNumeric(prevLATokenInfo); return; } - + if(ch == '=') { advanceChar(ErrorOnEOF.No); mixin(accept!"="); } - + else if(ch == '{') { advanceChar(ErrorOnEOF.No); mixin(accept!"{"); } - + else if(ch == '}') { advanceChar(ErrorOnEOF.No); mixin(accept!"}"); } - + else if(ch == ':') { advanceChar(ErrorOnEOF.No); mixin(accept!":"); } - + else if(ch == ';') { advanceChar(ErrorOnEOF.No); @@ -421,7 +421,7 @@ advanceChar(cnt, ErrorOnEOF.No); mixin(accept!"EOL"); } - + else if(isAlpha(ch) || ch == '_') lexIdentKeyword(); @@ -430,7 +430,7 @@ else if(ch == '`') lexRawString(); - + else if(ch == '\'') lexCharacter(); @@ -451,7 +451,7 @@ private void lexIdentKeyword() { assert(isAlpha(ch) || ch == '_'); - + // Keyword struct Key { @@ -471,10 +471,10 @@ keywords[4] = Key("null", Value(null )); keywordsInited = true; } - + foreach(ref key; keywords) key.failed = false; - + auto numKeys = keywords.length; do @@ -486,10 +486,10 @@ { case KeywordResult.Accept: mixin(accept!("Value", "key.value")); - + case KeywordResult.Continue: break; - + case KeywordResult.Failed: key.failed = true; numKeys--; @@ -520,13 +520,13 @@ { if(tokenLength == 0) assert(isAlpha(ch) || ch == '_'); - + while(!isEOF && isIdentChar(ch)) advanceChar(ErrorOnEOF.No); mixin(accept!"Ident"); } - + /// Lex regular string private void lexRegularString() { @@ -534,7 +534,7 @@ Appender!string buf; size_t spanStart = nextPos; - + // Doesn't include current character void updateBuf() { @@ -543,7 +543,7 @@ buf.put( source[spanStart..location.index] ); } - + advanceChar(ErrorOnEOF.Yes); while(ch != '"') { @@ -564,7 +564,7 @@ default: wasEscSequence = false; break; } } - + if(wasEscSequence) { advanceChar(ErrorOnEOF.Yes); @@ -582,7 +582,7 @@ advanceChar(ErrorOnEOF.Yes); } - + updateBuf(); advanceChar(ErrorOnEOF.No); // Skip closing double-quote mixin(accept!("Value", "buf.data")); @@ -592,21 +592,21 @@ private void lexRawString() { assert(ch == '`'); - + do advanceChar(ErrorOnEOF.Yes); while(ch != '`'); - + advanceChar(ErrorOnEOF.No); // Skip closing back-tick mixin(accept!("Value", "tokenData[1..$-1]")); } - + /// Lex character literal private void lexCharacter() { assert(ch == '\''); advanceChar(ErrorOnEOF.Yes); // Skip opening single-quote - + dchar value; if(ch == '\\') { @@ -634,25 +634,25 @@ mixin(accept!("Value", "value")); } - + /// Lex base64 binary literal private void lexBinary() { assert(ch == '['); advanceChar(ErrorOnEOF.Yes); - + void eatBase64Whitespace() { while(!isEOF && isWhite(ch)) { if(isNewline(ch)) advanceChar(ErrorOnEOF.Yes); - + if(!isEOF && isWhite(ch)) eatWhite(); } } - + eatBase64Whitespace(); // Iterates all valid base64 characters, ending at ']'. @@ -662,17 +662,17 @@ Lexer lexer; private bool isInited = false; private int numInputCharsMod4 = 0; - + @property bool empty() { if(lexer.ch == ']') { if(numInputCharsMod4 != 0) lexer.error("Length of Base64 encoding must be a multiple of 4. ("~to!string(numInputCharsMod4)~")"); - + return true; } - + return false; } @@ -680,7 +680,7 @@ { return lexer.ch; } - + void popFront() { auto lex = lexer; @@ -692,14 +692,14 @@ numInputCharsMod4++; numInputCharsMod4 %= 4; } - + isInited = true; } - + lex.advanceChar(lex.ErrorOnEOF.Yes); eatBase64Whitespace(); - + if(lex.isEOF) lex.error("Unexpected end of file."); @@ -707,13 +707,13 @@ { if(!lex.isBase64(lex.ch)) lex.error("Invalid character in base64 binary literal."); - + numInputCharsMod4++; numInputCharsMod4 %= 4; } } } - + // This is a slow ugly hack. It's necessary because Base64.decode // currently requires the source to have known length. //TODO: Remove this when DMD issue #9543 is fixed. @@ -729,7 +729,7 @@ outputBuf.put(ch); } } - + try //Base64.decode(Base64InputRange(this), OutputBuf()); Base64.decode(tmpBuf, OutputBuf()); @@ -737,11 +737,11 @@ //TODO: Starting with dmd 2.062, this should be a Base64Exception catch(Exception e) error("Invalid character in base64 binary literal."); - + advanceChar(ErrorOnEOF.No); // Skip ']' mixin(accept!("Value", "outputBuf.data")); } - + private BigInt toBigInt(bool isNegative, string absValue) { auto num = BigInt(absValue); @@ -759,14 +759,14 @@ { if(!isDigit(ch)) error("Expected a digit 0-9."); - + auto spanStart = location.index; - + do { advanceChar(ErrorOnEOF.No); } while(!isEOF && isDigit(ch)); - + return source[spanStart..location.index]; } @@ -811,7 +811,7 @@ mixin(accept!("Value", "num.toLong()")); } - + // Float (32-bit signed)? else if(ch == 'F' || ch == 'f') { @@ -819,7 +819,7 @@ advanceChar(ErrorOnEOF.No); mixin(accept!("Value", "value")); } - + // Double float (64-bit signed) with suffix? else if((ch == 'D' || ch == 'd') && !lookahead(':') ) @@ -828,7 +828,7 @@ advanceChar(ErrorOnEOF.No); mixin(accept!("Value", "value")); } - + // Decimal (128+ bits signed)? else if( (ch == 'B' || ch == 'b') && @@ -840,15 +840,15 @@ advanceChar(ErrorOnEOF.No); mixin(accept!("Value", "value")); } - + // Some floating point? else if(ch == '.') lexFloatingPoint(firstFragment); - + // Some date? else if(ch == '/' && hasNextCh && isDigit(nextCh)) lexDate(isNegative, firstFragment); - + // Some time span? else if(ch == ':' || ch == 'd') lexTimeSpan(isNegative, firstFragment); @@ -867,15 +867,15 @@ else error("Invalid integer suffix."); } - + /// Lex any floating-point literal (after the initial numeric fragment was lexed) private void lexFloatingPoint(string firstPart) { assert(ch == '.'); advanceChar(ErrorOnEOF.No); - + auto secondPart = lexNumericFragment(); - + try { // Double float (64-bit signed) with suffix? @@ -928,7 +928,7 @@ private Date makeDate(bool isNegative, string yearStr, string monthStr, string dayStr) { BigInt biTmp; - + biTmp = BigInt(yearStr); if(isNegative) biTmp = -biTmp; @@ -940,15 +940,15 @@ if(biTmp < 1 || biTmp > 12) error(tokenStart, "Date's month is out of range."); auto month = biTmp.toInt(); - + biTmp = BigInt(dayStr); if(biTmp < 1 || biTmp > 31) error(tokenStart, "Date's month is out of range."); auto day = biTmp.toInt(); - + return Date(year, month, day); } - + private DateTimeFrac makeDateTimeFrac( bool isNegative, Date date, string hourStr, string minuteStr, string secondStr, string millisecondStr @@ -960,12 +960,12 @@ if(biTmp < int.min || biTmp > int.max) error(tokenStart, "Datetime's hour is out of range."); auto numHours = biTmp.toInt(); - + biTmp = BigInt(minuteStr); if(biTmp < 0 || biTmp > int.max) error(tokenStart, "Datetime's minute is out of range."); auto numMinutes = biTmp.toInt(); - + int numSeconds = 0; if(secondStr != "") { @@ -974,7 +974,7 @@ error(tokenStart, "Datetime's second is out of range."); numSeconds = biTmp.toInt(); } - + int millisecond = 0; if(millisecondStr != "") { @@ -990,7 +990,7 @@ } Duration fracSecs = millisecond.msecs; - + auto offset = hours(numHours) + minutes(numMinutes) + seconds(numSeconds); if(isNegative) @@ -998,7 +998,7 @@ offset = -offset; fracSecs = -fracSecs; } - + return DateTimeFrac(DateTime(date) + offset, fracSecs); } @@ -1047,7 +1047,7 @@ else if(millisecondStr.length == 2) millisecond *= 10; } - + auto duration = dur!"days" (day) + dur!"hours" (hour) + @@ -1057,7 +1057,7 @@ if(isNegative) duration = -duration; - + return duration; } @@ -1067,7 +1067,7 @@ { if(str.length < 2) return Nullable!Duration(); // Unknown timezone - + if(str[0] != '+' && str[0] != '-') return Nullable!Duration(); // Unknown timezone @@ -1085,7 +1085,7 @@ numMinutesStr = str.find(':'); numHoursStr = str[1 .. $-numMinutesStr.length]; } - + long numHours = 0; long numMinutes = 0; bool isUnknown = false; @@ -1133,7 +1133,7 @@ } catch(ConvException e) isUnknown = true; - + if(isUnknown) return Nullable!Duration(); // Unknown timezone @@ -1144,12 +1144,12 @@ // Timezone valid return Nullable!Duration(timeZoneOffset); } - + /// Lex date or datetime (after the initial numeric fragment was lexed) private void lexDate(bool isDateNegative, string yearStr) { assert(ch == '/'); - + // Lex months advanceChar(ErrorOnEOF.Yes); // Skip '/' auto monthStr = lexNumericFragment(); @@ -1159,18 +1159,18 @@ error("Invalid date format: Missing days."); advanceChar(ErrorOnEOF.Yes); // Skip '/' auto dayStr = lexNumericFragment(); - + auto date = makeDate(isDateNegative, yearStr, monthStr, dayStr); if(!isEndOfNumber() && ch != '/') error("Dates cannot have suffixes."); - + // Date? if(isEOF) mixin(accept!("Value", "date")); - + auto endOfDate = location; - + while( !isEOF && ( ch == '\\' || ch == '/' || (isWhite(ch) && !isNewline(ch)) ) @@ -1190,7 +1190,7 @@ // Date? if(isEOF || (!isDigit(ch) && ch != '-')) mixin(accept!("Value", "date", "", "endOfDate.index")); - + auto startOfTime = location; // Is time negative? @@ -1200,7 +1200,7 @@ // Lex hours auto hourStr = ch == '.'? "" : lexNumericFragment(); - + // Lex minutes if(ch != ':') { @@ -1214,7 +1214,7 @@ } advanceChar(ErrorOnEOF.Yes); // Skip ':' auto minuteStr = lexNumericFragment(); - + // Lex seconds, if exists string secondStr; if(ch == ':') @@ -1222,7 +1222,7 @@ advanceChar(ErrorOnEOF.Yes); // Skip ':' secondStr = lexNumericFragment(); } - + // Lex milliseconds, if exists string millisecondStr; if(ch == '.') @@ -1232,25 +1232,25 @@ } auto dateTimeFrac = makeDateTimeFrac(isTimeNegative, date, hourStr, minuteStr, secondStr, millisecondStr); - + // Lex zone, if exists if(ch == '-') { advanceChar(ErrorOnEOF.Yes); // Skip '-' auto timezoneStart = location; - + if(!isAlpha(ch)) error("Invalid timezone format."); - + while(!isEOF && !isWhite(ch)) advanceChar(ErrorOnEOF.No); - + auto timezoneStr = source[timezoneStart.index..location.index]; if(timezoneStr.startsWith("GMT")) { auto isoPart = timezoneStr["GMT".length..$]; auto offset = getTimeZoneOffset(isoPart); - + if(offset.isNull()) { // Unknown time zone @@ -1259,18 +1259,16 @@ else { auto timezone = new immutable SimpleTimeZone(offset.get()); - static if (__VERSION__ >= 2067) auto fsecs = dateTimeFrac.fracSecs; - else auto fsecs = FracSec.from!"hnsecs"(dateTimeFrac.fracSecs.total!"hnsecs"); + auto fsecs = dateTimeFrac.fracSecs; mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, fsecs, timezone)")); } } - + try { auto timezone = PosixTimeZone.getTimeZone(timezoneStr); - if(timezone) { - static if (__VERSION__ >= 2067) auto fsecs = dateTimeFrac.fracSecs; - else auto fsecs = FracSec.from!"hnsecs"(dateTimeFrac.fracSecs.total!"hnsecs"); + if (timezone) { + auto fsecs = dateTimeFrac.fracSecs; mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, fsecs, timezone)")); } } @@ -1293,7 +1291,7 @@ private void lexTimeSpan(bool isNegative, string firstPart) { assert(ch == ':' || ch == 'd'); - + string dayStr = ""; string hourStr; @@ -1324,7 +1322,7 @@ error("Invalid time span format: Missing seconds."); advanceChar(ErrorOnEOF.Yes); // Skip ':' auto secondStr = lexNumericFragment(); - + // Lex milliseconds, if exists string millisecondStr = ""; if(ch == '.') @@ -1335,7 +1333,7 @@ if(!isEndOfNumber()) error("Time spans cannot have suffixes."); - + auto duration = makeDuration(isNegative, dayStr, hourStr, minuteStr, secondStr, millisecondStr); mixin(accept!("Value", "duration")); } @@ -1354,7 +1352,7 @@ if(isEOF) return; - + Location commentStart; State state = State.normal; bool consumeNewlines = false; @@ -1427,12 +1425,12 @@ } break; - + case State.lineComment: if(lookahead(&isNewline)) state = State.normal; break; - + case State.blockComment: if(ch == '*' && lookahead('/')) { @@ -1441,7 +1439,7 @@ } break; } - + advanceChar(ErrorOnEOF.No); if(isEOF) { @@ -1494,7 +1492,7 @@ if (is_same && test_locations) { is_same = actual.map!(t => t.location).equal(expected.map!(t => t.location)); } - + if(!is_same) { numErrors++; @@ -1548,7 +1546,7 @@ { writeln("Unittesting sdlang lexer..."); stdout.flush(); - + testLex("", []); testLex(" ", []); testLex("\\\n", []); @@ -1604,7 +1602,7 @@ testLexThrows("<"); testLexThrows("*"); testLexThrows(`\`); - + // Integers testLex( "7", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]); testLex( "-7", [ Token(symbol!"Value",loc,Value(cast( int)-7)) ]); @@ -1624,12 +1622,12 @@ testLexThrows("7A"); testLexThrows("-A"); testLexThrows(`-""`); - + testLex("7;", [ Token(symbol!"Value",loc,Value(cast(int)7)), Token(symbol!"EOL",loc), ]); - + // Floats testLex("1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); testLex("1.2f" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]); @@ -1751,7 +1749,7 @@ testLexThrows(`'\`); testLexThrows(`'\'`); testLexThrows("'"); - + // Unicode testLex("日本語", [ Token(symbol!"Ident",loc,Value(null), "日本語") ]); testLex("`おはよう、日本。`", [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]); @@ -1792,18 +1790,18 @@ testLex( "2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); testLex("-2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]); testLex( "2013/2/22 07:53:34", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34)))) ]); - testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123)))) ]); - testLex( "2013/2/22 07:53:34.12", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(120)))) ]); - testLex( "2013/2/22 07:53:34.1", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(100)))) ]); - testLex( "2013/2/22 07:53.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123)))) ]); + testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs))) ]); + testLex( "2013/2/22 07:53:34.12", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 120.msecs))) ]); + testLex( "2013/2/22 07:53:34.1", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), 100.msecs))) ]); + testLex( "2013/2/22 07:53.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs))) ]); testLex( "2013/2/22 34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0)))) ]); - testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), FracSec.from!"msecs"(123)))) ]); - testLex( "2013/2/22 34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), FracSec.from!"msecs"(123)))) ]); + testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), 123.msecs))) ]); + testLex( "2013/2/22 34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), 123.msecs))) ]); testLex( "2013/2/22 -34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0)))) ]); - testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), FracSec.from!"msecs"(-123)))) ]); - testLex( "2013/2/22 -34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), FracSec.from!"msecs"(-123)))) ]); + testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), -123.msecs))) ]); + testLex( "2013/2/22 -34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), -123.msecs))) ]); testLexThrows("2013/2/22 07:53a"); testLexThrows("2013/2/22 07:53f"); @@ -1851,12 +1849,12 @@ testLex( "2013/2/22 07:53:34-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(0) )))) ]); testLex( "2013/2/22 07:53:34-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); testLex( "2013/2/22 07:53:34-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); - testLex( "2013/2/22 07:53.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(0) )))) ]); - testLex( "2013/2/22 07:53.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); - testLex( "2013/2/22 07:53.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); + testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); + testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); + testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); + testLex( "2013/2/22 07:53.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(0) )))) ]); + testLex( "2013/2/22 07:53.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]); + testLex( "2013/2/22 07:53.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); testLex( "2013/2/22 -34:65-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]); @@ -1871,7 +1869,7 @@ Token testTokenUnknownTimeZone(string tzName) { auto dateTime = DateTime(2013, 2, 22, 7, 53, 0); - auto frac = FracSec.from!"msecs"(0); + auto frac = 0.msecs; return Token( symbol!"Value", loc, Value(DateTimeFracUnknownZone(dateTime,frac,tzName)) ); } testLex("2013/2/22 07:53-GMT+", [ testTokenUnknownTimeZone("GMT+") ]); @@ -1912,13 +1910,13 @@ testLex("2013/2/22 07:53-GMT+00004:003", [ testTokenUnknownTimeZone("GMT+00004:003") ]); // DateTime, with unknown timezone - testLex( "2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"( 0), "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]); - testLex("-2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]); - testLex( "2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]); - testLex("-2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), "Bogus/Foo"))) ]); - testLex( "2013/2/22 07:53.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), "Bogus/Foo"))) ]); + testLex( "2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]); + testLex("-2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53, 0), 0.msecs, "Bogus/Foo"))) ]); + testLex( "2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); + testLex("-2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), 0.msecs, "Bogus/Foo"))) ]); + testLex( "2013/2/22 07:53:34-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 0.msecs, "Bogus/Foo"))) ]); + testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), 123.msecs, "Bogus/Foo"))) ]); + testLex( "2013/2/22 07:53.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), 123.msecs, "Bogus/Foo"))) ]); // Time Span testLex( "12:14:42", [ Token(symbol!"Value",loc,Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0))) ]); @@ -1941,12 +1939,12 @@ Token(symbol!"Ident",loc,Value( null),"foo."), Token(symbol!"Value",loc,Value(cast(int)7)) ]); - + testLex(` namespace:person "foo" "bar" 1 23L name.first="ひとみ" name.last="Smith" { namespace:age 37; namespace:favorite_color "blue" // comment somedate 2013/2/22 07:53 -- comment - + inventory /* comment */ { socks } @@ -2000,7 +1998,7 @@ Token(symbol!"}", loc, Value(null), "}"), Token(symbol!"EOL", loc, Value(null), "\n"), ]); - + if(numErrors > 0) stderr.writeln(numErrors, " failed test(s)"); } @@ -2021,7 +2019,7 @@ { writeln("lexer: Regression test issue #11..."); stdout.flush(); - + void test(string input) { testLex( diff --git a/source/dub/internal/sdlang/package.d b/source/dub/internal/sdlang/package.d index c128a46..7f1c67a 100644 --- a/source/dub/internal/sdlang/package.d +++ b/source/dub/internal/sdlang/package.d @@ -68,7 +68,7 @@ stderr.writeln("Usage: sdlang [lex|parse|to-sdl] filename.sdl"); return 1; } - + auto filename = args[2]; try @@ -85,7 +85,7 @@ stderr.writeln(e.msg); return 1; } - + return 0; } @@ -93,28 +93,28 @@ { auto source = cast(string)read(filename); auto lexer = new Lexer(source, filename); - + foreach(tok; lexer) { // Value string value; if(tok.symbol == symbol!"Value") value = tok.value.hasValue? toString(tok.value.type) : "{null}"; - + value = value==""? "\t" : "("~value~":"~tok.value.toString()~") "; // Data auto data = tok.data.replace("\n", "").replace("\r", ""); if(data != "") data = "\t|"~tok.data~"|"; - + // Display writeln( tok.location.toString, ":\t", tok.symbol.name, value, data ); - + if(tok.symbol.name == "Error") break; } diff --git a/source/dub/internal/sdlang/parser.d b/source/dub/internal/sdlang/parser.d index 3e32690..8ccf119 100644 --- a/source/dub/internal/sdlang/parser.d +++ b/source/dub/internal/sdlang/parser.d @@ -173,13 +173,13 @@ private struct PullParser { private Lexer lexer; - + private struct IDFull { string namespace; string name; } - + private void error(string msg) { error(lexer.front.location, msg); @@ -189,20 +189,20 @@ { throw new SDLangParseException(loc, "Error: "~msg); } - + private InputVisitor!(PullParser, ParserEvent) v; - + void visit(InputVisitor!(PullParser, ParserEvent) v) { this.v = v; parseRoot(); } - + private void emit(Event)(Event event) { v.yield( ParserEvent(event) ); } - + /// ::= EOF (Lookaheads: Anything) private void parseRoot() { @@ -213,11 +213,11 @@ emit( FileStartEvent(startLocation) ); parseTags(); - + auto token = lexer.front; if(!token.matches!"EOF"()) error("Expected end-of-file, not " ~ token.symbol.name); - + emit( FileEndEvent(token.location) ); } @@ -283,7 +283,7 @@ parseAttributes(); parseOptChild(); parseTagTerminator(); - + emit( TagEndEvent() ); } @@ -365,7 +365,7 @@ auto value = token.value; //trace("In tag '", parent.fullName, "', found value: ", value); emit( ValueEvent(token.location, value) ); - + lexer.popFront(); } else @@ -401,21 +401,21 @@ auto token = lexer.front; if(!token.matches!"Ident"()) error("Expected attribute name, not "~token.symbol.name); - + auto id = parseIDFull(); - + token = lexer.front; if(!token.matches!"="()) error("Expected '=' after attribute name, not "~token.symbol.name); - + lexer.popFront(); token = lexer.front; if(!token.matches!"Value"()) error("Expected attribute value, not "~token.symbol.name); - + //trace("In tag '", parent.fullName, "', found attribute '", attr.fullName, "'"); emit( AttributeEvent(token.location, id.namespace, id.name, token.value) ); - + lexer.popFront(); } @@ -432,10 +432,10 @@ token = lexer.front; if(!token.matches!"EOL"()) error("Expected newline or semicolon after '{', not "~token.symbol.name); - + lexer.popFront(); parseTags(); - + token = lexer.front; if(!token.matches!"}"()) error("Expected '}' after child tags, not "~token.symbol.name); @@ -447,7 +447,7 @@ // Do nothing, no error. } } - + /// /// ::= EOL (Lookahead: EOL) /// | {empty} (Lookahead: EOF) @@ -472,12 +472,12 @@ private struct DOMParser { Lexer lexer; - + Tag parseRoot() { auto currTag = new Tag(null, null, "root"); currTag.location = Location(lexer.filename, 0, 0, 0); - + auto parser = PullParser(lexer); auto eventRange = inputVisitor!ParserEvent( parser ); foreach(event; eventRange) @@ -486,7 +486,7 @@ { auto newTag = new Tag(currTag, e.namespace, e.name); newTag.location = e.location; - + currTag = newTag; } else if(event.peek!TagEndEvent()) @@ -518,7 +518,7 @@ else parser.error("Internal Error: Received unknown parser event"); } - + return currTag; } } diff --git a/source/dub/internal/sdlang/symbol.d b/source/dub/internal/sdlang/symbol.d index 574d97f..04de244 100644 --- a/source/dub/internal/sdlang/symbol.d +++ b/source/dub/internal/sdlang/symbol.d @@ -50,7 +50,7 @@ { return _name; } - + @disable this(); private this(string name) { diff --git a/source/dub/internal/sdlang/token.d b/source/dub/internal/sdlang/token.d index b5f8f4a..a86ae5d 100644 --- a/source/dub/internal/sdlang/token.d +++ b/source/dub/internal/sdlang/token.d @@ -1,4 +1,4 @@ -// SDLang-D +// SDLang-D // Written in the D programming language. module dub.internal.sdlang.token; @@ -23,14 +23,9 @@ struct DateTimeFrac { this(DateTime dt, Duration fs) { this.dateTime = dt; this.fracSecs = fs; } - this(DateTime dt, FracSec fs) { this.dateTime = dt; this.fracSecs = fs.hnsecs.hnsecs; } DateTime dateTime; Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } } /++ @@ -47,10 +42,6 @@ { DateTime dateTime; Duration fracSecs; - deprecated("Use fracSecs instead.") { - @property FracSec fracSec() { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); } - @property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; } - } string timeZone; bool opEquals(const DateTimeFracUnknownZone b) const @@ -247,10 +238,7 @@ void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char)) { - static if (__VERSION__ >= 2067) - auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs); - else - auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSec); + auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs); toSDLString(dateTimeFrac, sink); sink.put("-"); @@ -286,12 +274,7 @@ sink.put("+"); long hours, minutes; - static if (__VERSION__ >= 2066) - offset.split!("hours", "minutes")(hours, minutes); - else { - hours = offset.hours; - minutes = offset.minutes; - } + offset.split!("hours", "minutes")(hours, minutes); sink.put("%.2s".format(hours)); sink.put(":"); @@ -326,14 +309,7 @@ } long hours, minutes, seconds, msecs; - static if (__VERSION__ >= 2066) - value.split!("hours", "minutes", "seconds", "msecs")(hours, minutes, seconds, msecs); - else { - hours = value.hours; - minutes = value.minutes; - seconds = value.seconds; - msecs = value.fracSec.msecs; - } + value.split!("hours", "minutes", "seconds", "msecs")(hours, minutes, seconds, msecs); sink.put("%.2s".format(hours)); sink.put(':'); @@ -500,15 +476,15 @@ assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15"); // DateTimeFrac w/ Frac - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"(123))).toSDLString() == "2004/10/31 14:30:15.123"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"(120))).toSDLString() == "2004/10/31 14:30:15.120"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"(100))).toSDLString() == "2004/10/31 14:30:15.100"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"( 12))).toSDLString() == "2004/10/31 14:30:15.012"); - assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"( 1))).toSDLString() == "2004/10/31 14:30:15.001"); - assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), FracSec.from!"msecs"(123))).toSDLString() == "-2004/10/31 14:30:15.123"); + assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123"); + assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120"); + assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100"); + assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 12.msecs)).toSDLString() == "2004/10/31 14:30:15.012"); + assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 1.msecs)).toSDLString() == "2004/10/31 14:30:15.001"); + assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123"); // DateTimeFracUnknownZone - assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"(123), "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar"); + assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar"); // SysTime assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00"); @@ -516,7 +492,7 @@ assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10"); assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30"); assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03"); - assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00"); + assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00"); // Duration assert( "12:14:42" == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0)).toSDLString()); diff --git a/source/dub/internal/sdlang/util.d b/source/dub/internal/sdlang/util.d index d73b221..c2e2ac7 100644 --- a/source/dub/internal/sdlang/util.d +++ b/source/dub/internal/sdlang/util.d @@ -29,14 +29,14 @@ int line; /// Zero-indexed int col; /// Zero-indexed, Tab counts as 1 size_t index; /// Index into the source - + this(int line, int col, int index) { this.line = line; this.col = col; this.index = index; } - + this(string file, int line, int col, int index) { this.file = file; @@ -44,7 +44,7 @@ this.col = col; this.index = index; } - + string toString() { return "%s(%s:%s)".format(file, line+1, col+1); @@ -82,7 +82,7 @@ else if(ti == typeid( Duration )) return "Duration"; else if(ti == typeid( ubyte[] )) return "ubyte[]"; else if(ti == typeid( typeof(null) )) return "null"; - + return "{unknown}"; } diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index 83fb0ab..6380791 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -14,63 +14,81 @@ import dub.compilers.buildsettings : BuildSettings; import dub.version_; -// todo: cleanup imports. -import core.thread; -import std.algorithm : startsWith; -import std.array; -import std.conv; -import std.exception; +import core.time : Duration; +import std.algorithm : canFind, startsWith; +import std.array : appender, array; +import std.conv : to; +import std.exception : enforce; import std.file; +import std.string : format; import std.process; -import std.string; import std.traits : isIntegral; -import std.typecons; -import std.zip; -version(DubUseCurl) import std.net.curl; - - -private Path[] temporary_files; - -Path getTempDir() +version(DubUseCurl) { - return Path(std.file.tempDir()); + import std.net.curl; + static if (__VERSION__ > 2075) public import std.net.curl : HTTPStatusException; } -Path getTempFile(string prefix, string extension = null) + +private NativePath[] temporary_files; + +NativePath getTempDir() +{ + return NativePath(std.file.tempDir()); +} + +NativePath getTempFile(string prefix, string extension = null) { import std.uuid : randomUUID; + import std.array: replace; - auto path = getTempDir() ~ (prefix ~ "-" ~ randomUUID.toString() ~ extension); + string fileName = prefix ~ "-" ~ randomUUID.toString() ~ extension; + + if (extension !is null && extension == ".d") + fileName = fileName.replace("-", "_"); + + auto path = getTempDir() ~ fileName; temporary_files ~= path; return path; } -// lockfile based on atomic mkdir -struct LockFile -{ - bool opCast(T:bool)() { return !!path; } - ~this() { if (path) rmdir(path); } - string path; -} +/** + Obtain a lock for a file at the given path. If the file cannot be locked + within the given duration, an exception is thrown. The file will be created + if it does not yet exist. Deleting the file is not safe as another process + could create a new file with the same name. + The returned lock will get unlocked upon destruction. -auto tryLockFile(string path) + Params: + path = path to file that gets locked + timeout = duration after which locking failed + Returns: + The locked file or an Exception on timeout. +*/ +auto lockFile(string path, Duration timeout) { - import std.file; - if (collectException(mkdir(path))) - return LockFile(null); - return LockFile(path); -} + import core.thread : Thread; + import std.datetime, std.stdio : File; + import std.algorithm : move; -auto lockFile(string path, Duration wait) -{ - import std.datetime, std.file; + // Just a wrapper to hide (and destruct) the locked File. + static struct LockFile + { + // The Lock can't be unlinked as someone could try to lock an already + // opened fd while a new file with the same name gets created. + // Exclusive filesystem locks (O_EXCL, mkdir) could be deleted but + // aren't automatically freed when a process terminates, see #1149. + private File f; + } + + auto file = File(path, "w"); auto t0 = Clock.currTime(); auto dur = 1.msecs; while (true) { - if (!collectException(mkdir(path))) - return LockFile(path); - enforce(Clock.currTime() - t0 < wait, "Failed to lock '"~path~"'."); + if (file.tryLock()) + return LockFile(move(file)); + enforce(Clock.currTime() - t0 < timeout, "Failed to lock '"~path~"'."); if (dur < 1024.msecs) // exponentially increase sleep time dur *= 2; Thread.sleep(dur); @@ -87,13 +105,13 @@ } } -bool isEmptyDir(Path p) { +bool isEmptyDir(NativePath p) { foreach(DirEntry e; dirEntries(p.toNativeString(), SpanMode.shallow)) return false; return true; } -bool isWritableDir(Path p, bool create_if_missing = false) +bool isWritableDir(NativePath p, bool create_if_missing = false) { import std.random; auto fname = p ~ format("__dub_write_test_%08X", uniform(0, uint.max)); @@ -104,7 +122,7 @@ return true; } -Json jsonFromFile(Path file, bool silent_fail = false) { +Json jsonFromFile(NativePath file, bool silent_fail = false) { if( silent_fail && !existsFile(file) ) return Json.emptyObject; auto f = openFile(file.toNativeString(), FileMode.read); scope(exit) f.close(); @@ -112,7 +130,41 @@ return parseJsonString(text, file.toNativeString()); } -Json jsonFromZip(Path zip, string filename) { +/** + Read package info file content from archive. + File needs to be in root folder or in first + sub folder. + + Params: + zip = path to archive file + fileName = Package file name + Returns: + package file content. +*/ +string packageInfoFileFromZip(NativePath zip, out string fileName) { + import std.zip : ZipArchive, ArchiveMember; + import dub.package_ : packageInfoFiles; + + auto f = openFile(zip, FileMode.read); + ubyte[] b = new ubyte[cast(size_t)f.size]; + f.rawRead(b); + f.close(); + auto archive = new ZipArchive(b); + alias PSegment = typeof (NativePath.init.head); + foreach (ArchiveMember am; archive.directory) { + auto path = NativePath(am.name).bySegment.array; + foreach (fil; packageInfoFiles) { + if ((path.length == 1 && path[0] == fil.filename) || (path.length == 2 && path[$-1].toString == fil.filename)) { + fileName = fil.filename; + return stripUTF8Bom(cast(string) archive.expand(archive.directory[am.name])); + } + } + } + throw new Exception("No package descriptor found"); +} + +Json jsonFromZip(NativePath zip, string filename) { + import std.zip : ZipArchive; auto f = openFile(zip, FileMode.read); ubyte[] b = new ubyte[cast(size_t)f.size]; f.rawRead(b); @@ -122,7 +174,7 @@ return parseJsonString(text, zip.toNativeString~"/"~filename); } -void writeJsonFile(Path path, Json json) +void writeJsonFile(NativePath path, Json json) { auto f = openFile(path, FileMode.createTrunc); scope(exit) f.close(); @@ -130,10 +182,10 @@ } /// Performs a write->delete->rename sequence to atomically "overwrite" the destination file -void atomicWriteJsonFile(Path path, Json json) +void atomicWriteJsonFile(NativePath path, Json json) { import std.random : uniform; - auto tmppath = path[0 .. $-1] ~ format("%s.%s.tmp", path.head, uniform(0, int.max)); + auto tmppath = path.parentPath ~ format("%s.%s.tmp", path.head, uniform(0, int.max)); auto f = openFile(tmppath, FileMode.createTrunc); scope (failure) { f.close(); @@ -150,7 +202,7 @@ return p[$-1] == '/'; } -bool existsDirectory(Path path) { +bool existsDirectory(NativePath path) { if( !existsFile(path) ) return false; auto fi = getFileInfo(path); return fi.isDirectory; @@ -185,49 +237,114 @@ Pid pid; pid = spawnShell(cmd, stdin, childStdout, childStderr, env, config); auto exitcode = pid.wait(); - enforce(exitcode == 0, "Command failed with exit code "~to!string(exitcode)); + enforce(exitcode == 0, "Command failed with exit code " + ~ to!string(exitcode) ~ ": " ~ cmd); } } +version(DubUseCurl) { + /++ + Exception thrown on HTTP request failures, e.g. 404 Not Found. + +/ + static if (__VERSION__ <= 2075) class HTTPStatusException : CurlException + { + /++ + Params: + status = The HTTP status code. + msg = The message for the exception. + file = The file where the exception occurred. + line = The line number where the exception occurred. + next = The previous exception in the chain of exceptions, if any. + +/ + @safe pure nothrow + this( + int status, + string msg, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null) + { + this.status = status; + super(msg, file, line, next); + } + + int status; /// The HTTP status code + } +} else version (Have_vibe_d_http) { + public import vibe.http.common : HTTPStatusException; +} + /** Downloads a file from the specified URL. Any redirects will be followed until the actual file resource is reached or if the redirection limit of 10 is reached. Note that only HTTP(S) is currently supported. + + The download times out if a connection cannot be established within + `timeout` ms, or if the average transfer rate drops below 10 bytes / s for + more than `timeout` seconds. Pass `0` as `timeout` to disable both timeout + mechanisms. + + Note: Timeouts are only implemented when curl is used (DubUseCurl). */ -void download(string url, string filename) +void download(string url, string filename, uint timeout = 8) { version(DubUseCurl) { auto conn = HTTP(); - setupHTTPClient(conn); + setupHTTPClient(conn, timeout); logDebug("Storing %s...", url); - std.net.curl.download(url, filename, conn); - enforce(conn.statusLine.code < 400, - format("Failed to download %s: %s %s", - url, conn.statusLine.code, conn.statusLine.reason)); - } else version (Have_vibe_d) { + static if (__VERSION__ <= 2075) + { + try + std.net.curl.download(url, filename, conn); + catch (CurlException e) + { + if (e.msg.canFind("404")) + throw new HTTPStatusException(404, e.msg); + throw e; + } + } + else + { + std.net.curl.download(url, filename, conn); + // workaround https://issues.dlang.org/show_bug.cgi?id=18318 + auto sl = conn.statusLine; + logDebug("Download %s %s", url, sl); + if (sl.code / 100 != 2) + throw new HTTPStatusException(sl.code, + "Downloading %s failed with %d (%s).".format(url, sl.code, sl.reason)); + } + } else version (Have_vibe_d_http) { import vibe.inet.urltransfer; vibe.inet.urltransfer.download(url, filename); } else assert(false); } /// ditto -void download(URL url, Path filename) +void download(URL url, NativePath filename, uint timeout = 8) { - download(url.toString(), filename.toNativeString()); + download(url.toString(), filename.toNativeString(), timeout); } /// ditto -ubyte[] download(string url) +ubyte[] download(string url, uint timeout = 8) { version(DubUseCurl) { auto conn = HTTP(); - setupHTTPClient(conn); + setupHTTPClient(conn, timeout); logDebug("Getting %s...", url); - auto ret = cast(ubyte[])get(url, conn); - enforce(conn.statusLine.code < 400, - format("Failed to GET %s: %s %s", - url, conn.statusLine.code, conn.statusLine.reason)); - return ret; - } else version (Have_vibe_d) { + static if (__VERSION__ <= 2075) + { + try + return cast(ubyte[])get(url, conn); + catch (CurlException e) + { + if (e.msg.canFind("404")) + throw new HTTPStatusException(404, e.msg); + throw e; + } + } + else + return cast(ubyte[])get(url, conn); + } else version (Have_vibe_d_http) { import vibe.inet.urltransfer; import vibe.stream.operations; ubyte[] ret; @@ -236,15 +353,111 @@ } else assert(false); } /// ditto -ubyte[] download(URL url) +ubyte[] download(URL url, uint timeout = 8) { - return download(url.toString()); + return download(url.toString(), timeout); +} + +/** + Downloads a file from the specified URL with retry logic. + + Downloads a file from the specified URL with up to n tries on failure + Throws: `Exception` if the download failed or `HTTPStatusException` after the nth retry or + on "unrecoverable failures" such as 404 not found + Otherwise might throw anything else that `download` throws. + See_Also: download + + The download times out if a connection cannot be established within + `timeout` ms, or if the average transfer rate drops below 10 bytes / s for + more than `timeout` seconds. Pass `0` as `timeout` to disable both timeout + mechanisms. + + Note: Timeouts are only implemented when curl is used (DubUseCurl). +**/ +void retryDownload(URL url, NativePath filename, size_t retryCount = 3, uint timeout = 8) +{ + foreach(i; 0..retryCount) { + version(DubUseCurl) { + try { + download(url, filename, timeout); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + if (i == retryCount - 1) throw e; + else continue; + } + } + catch(CurlException e) { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + continue; + } + } + else + { + try { + download(url, filename); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + if (i == retryCount - 1) throw e; + else continue; + } + } + } + } + throw new Exception("Failed to download %s".format(url)); +} + +///ditto +ubyte[] retryDownload(URL url, size_t retryCount = 3, uint timeout = 8) +{ + foreach(i; 0..retryCount) { + version(DubUseCurl) { + try { + return download(url, timeout); + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + if (i == retryCount - 1) throw e; + else continue; + } + } + catch(CurlException e) { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + continue; + } + } + else + { + try { + return download(url); + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else { + logDebug("Failed to download %s (Attempt %s of %s)", url, i + 1, retryCount); + if (i == retryCount - 1) throw e; + else continue; + } + } + } + } + throw new Exception("Failed to download %s".format(url)); } /// Returns the current DUB version in semantic version format string getDUBVersion() { import dub.version_; + import std.array : split, join; // convert version string to valid SemVer format auto verstr = dubVersion; if (verstr.startsWith("v")) verstr = verstr[1 .. $]; @@ -257,8 +470,67 @@ return verstr; } + +/** + Get current executable's path if running as DUB executable, + or find a DUB executable if DUB is used as a library. + For the latter, the following locations are checked in order: + $(UL + $(LI current working directory) + $(LI same directory as `compilerBinary` (if supplied)) + $(LI all components of the `$PATH` variable) + ) + Params: + compilerBinary = optional path to a D compiler executable, used to locate DUB executable + Returns: + The path to a valid DUB executable + Throws: + an Exception if no valid DUB executable is found +*/ +public string getDUBExePath(in string compilerBinary=null) +{ + version(DubApplication) { + import std.file : thisExePath; + return thisExePath(); + } + else { + // this must be dub as a library + import std.algorithm : filter, map, splitter; + import std.array : array; + import std.file : exists, getcwd; + import std.path : chainPath, dirName; + import std.range : chain, only, take; + import std.process : environment; + + version(Windows) { + enum exeName = "dub.exe"; + enum pathSep = ';'; + } + else { + enum exeName = "dub"; + enum pathSep = ':'; + } + + auto dubLocs = only( + getcwd().chainPath(exeName), + compilerBinary.dirName.chainPath(exeName), + ) + .take(compilerBinary.length ? 2 : 1) + .chain( + environment.get("PATH", "") + .splitter(pathSep) + .map!(p => p.chainPath(exeName)) + ) + .filter!exists; + + enforce(!dubLocs.empty, "Could not find DUB executable"); + return dubLocs.front.array; + } +} + + version(DubUseCurl) { - void setupHTTPClient(ref HTTP conn) + void setupHTTPClient(ref HTTP conn, uint timeout) { static if( is(typeof(&conn.verifyPeer)) ) conn.verifyPeer = false; @@ -266,6 +538,18 @@ auto proxy = environment.get("http_proxy", null); if (proxy.length) conn.proxy = proxy; + auto noProxy = environment.get("no_proxy", null); + if (noProxy.length) conn.handle.set(CurlOption.noproxy, noProxy); + + conn.handle.set(CurlOption.encoding, ""); + if (timeout) { + // connection (TLS+TCP) times out after 8s + conn.handle.set(CurlOption.connecttimeout, timeout); + // transfers time out after 8s below 10 byte/s + conn.handle.set(CurlOption.low_speed_limit, 10); + conn.handle.set(CurlOption.low_speed_time, 5); + } + conn.addRequestHeader("User-Agent", "dub/"~getDUBVersion()~" (std.net.curl; +https://github.com/rejectedsoftware/dub)"); } } @@ -374,25 +658,27 @@ return ret.data; } -string determineModuleName(BuildSettings settings, Path file, Path base_path) +string determineModuleName(BuildSettings settings, NativePath file, NativePath base_path) { import std.algorithm : map; + import std.array : array; + import std.range : walkLength; assert(base_path.absolute); if (!file.absolute) file = base_path ~ file; size_t path_skip = 0; - foreach (ipath; settings.importPaths.map!(p => Path(p))) { + foreach (ipath; settings.importPaths.map!(p => NativePath(p))) { if (!ipath.absolute) ipath = base_path ~ ipath; assert(!ipath.empty); - if (file.startsWith(ipath) && ipath.length > path_skip) - path_skip = ipath.length; + if (file.startsWith(ipath) && ipath.bySegment.walkLength > path_skip) + path_skip = ipath.bySegment.walkLength; } enforce(path_skip > 0, format("Source file '%s' not found in any import path.", file.toNativeString())); - auto mpath = file[path_skip .. file.length]; + auto mpath = file.bySegment.array[path_skip .. $]; auto ret = appender!string; //search for module keyword in file @@ -427,43 +713,37 @@ static Regex!char comments_pattern, module_pattern; if (!regex_initialized) { - comments_pattern = regex(`(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)`, "g"); + comments_pattern = regex(`//[^\r\n]*\r?\n?|/\*.*?\*/|/\+.*\+/`, "g"); module_pattern = regex(`module\s+([\w\.]+)\s*;`, "g"); regex_initialized = true; } - content = replaceAll(content, comments_pattern, ""); + content = replaceAll(content, comments_pattern, " "); auto result = matchFirst(content, module_pattern); - string moduleName; - if(!result.empty) moduleName = result.front; + if (!result.empty) return result[1]; - if (moduleName.length >= 7) moduleName = moduleName[7..$-1]; - - return moduleName; + return null; } unittest { - //test empty string - string name = getModuleNameFromContent(""); - assert(name == "", "can't get module name from empty string"); - - //test simple name - name = getModuleNameFromContent("module myPackage.myModule;"); - assert(name == "myPackage.myModule", "can't parse module name"); - - //test if it can ignore module inside comments - name = getModuleNameFromContent("/** - module fakePackage.fakeModule; - */ - module myPackage.myModule;"); - - assert(name == "myPackage.myModule", "can't parse module name"); - - name = getModuleNameFromContent("//module fakePackage.fakeModule; - module myPackage.myModule;"); - - assert(name == "myPackage.myModule", "can't parse module name"); + assert(getModuleNameFromContent("") == ""); + assert(getModuleNameFromContent("module myPackage.myModule;") == "myPackage.myModule"); + assert(getModuleNameFromContent("module \t\n myPackage.myModule \t\r\n;") == "myPackage.myModule"); + assert(getModuleNameFromContent("// foo\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/*\nfoo\n*/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/+\nfoo\n+/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/***\nfoo\n***/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/+++\nfoo\n+++/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("// module foo;\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/* module foo; */\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/+ module foo; +/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("/+ /+ module foo; +/ +/\nmodule bar;") == "bar"); + assert(getModuleNameFromContent("// module foo;\nmodule bar; // module foo;") == "bar"); + assert(getModuleNameFromContent("// module foo;\nmodule// module foo;\nbar//module foo;\n;// module foo;") == "bar"); + assert(getModuleNameFromContent("/* module foo; */\nmodule/*module foo;*/bar/*module foo;*/;") == "bar", getModuleNameFromContent("/* module foo; */\nmodule/*module foo;*/bar/*module foo;*/;")); + assert(getModuleNameFromContent("/+ /+ module foo; +/ module foo; +/ module bar;") == "bar"); + //assert(getModuleNameFromContent("/+ /+ module foo; +/ module foo; +/ module bar/++/;") == "bar"); // nested comments require a context-free parser! } /** diff --git a/source/dub/internal/vibecompat/core/file.d b/source/dub/internal/vibecompat/core/file.d index afacf52..26321d4 100644 --- a/source/dub/internal/vibecompat/core/file.d +++ b/source/dub/internal/vibecompat/core/file.d @@ -25,35 +25,36 @@ /* Add output range support to File */ struct RangeFile { +@safe: std.stdio.File file; - void put(in ubyte[] bytes) { file.rawWrite(bytes); } - void put(in char[] str) { put(cast(ubyte[])str); } - void put(char ch) { put((&ch)[0 .. 1]); } + void put(in ubyte[] bytes) @trusted { file.rawWrite(bytes); } + void put(in char[] str) { put(cast(const(ubyte)[])str); } + void put(char ch) @trusted { put((&ch)[0 .. 1]); } void put(dchar ch) { char[4] chars; put(chars[0 .. encode(chars, ch)]); } ubyte[] readAll() { - auto sz = file.size; + auto sz = this.size; enforce(sz <= size_t.max, "File is too big to read to memory."); - file.seek(0, SEEK_SET); + () @trusted { file.seek(0, SEEK_SET); } (); auto ret = new ubyte[cast(size_t)sz]; rawRead(ret); return ret; } - void rawRead(ubyte[] dst) { enforce(file.rawRead(dst).length == dst.length, "Failed to readall bytes from file."); } + void rawRead(ubyte[] dst) @trusted { enforce(file.rawRead(dst).length == dst.length, "Failed to readall bytes from file."); } void write(string str) { put(str); } - void close() { file.close(); } - void flush() { file.flush(); } - @property ulong size() { return file.size; } + void close() @trusted { file.close(); } + void flush() @trusted { file.flush(); } + @property ulong size() @trusted { return file.size; } } /** Opens a file stream with the specified mode. */ -RangeFile openFile(Path path, FileMode mode = FileMode.read) +RangeFile openFile(NativePath path, FileMode mode = FileMode.read) { string fmode; final switch(mode){ @@ -69,14 +70,14 @@ /// ditto RangeFile openFile(string path, FileMode mode = FileMode.read) { - return openFile(Path(path), mode); + return openFile(NativePath(path), mode); } /** Moves or renames a file. */ -void moveFile(Path from, Path to) +void moveFile(NativePath from, NativePath to) { moveFile(from.toNativeString(), to.toNativeString()); } @@ -92,8 +93,8 @@ Note that attributes and time stamps are currently not retained. Params: - from = Path of the source file - to = Path for the destination file + from = NativePath of the source file + to = NativePath for the destination file overwrite = If true, any file existing at the destination path will be overwritten. If this is false, an excpetion will be thrown should a file already exist at the destination path. @@ -101,7 +102,7 @@ Throws: An Exception if the copy operation fails for some reason. */ -void copyFile(Path from, Path to, bool overwrite = false) +void copyFile(NativePath from, NativePath to, bool overwrite = false) { enforce(existsFile(from), "Source file does not exist."); @@ -112,46 +113,58 @@ removeFile(to); } - .copy(from.toNativeString(), to.toNativeString()); - - // try to preserve ownership/permissions in Posix - version (Posix) { - import core.sys.posix.sys.stat; - import core.sys.posix.unistd; - import std.utf; - auto cspath = toUTFz!(const(char)*)(from.toNativeString()); - auto cdpath = toUTFz!(const(char)*)(to.toNativeString()); - stat_t st; - enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file."); - if (chown(cdpath, st.st_uid, st.st_gid) != 0) - st.st_mode &= ~(S_ISUID | S_ISGID); - chmod(cdpath, st.st_mode); + static if (is(PreserveAttributes)) + { + .copy(from.toNativeString(), to.toNativeString(), PreserveAttributes.yes); + } + else + { + .copy(from.toNativeString(), to.toNativeString()); + // try to preserve ownership/permissions in Posix + version (Posix) { + import core.sys.posix.sys.stat; + import core.sys.posix.unistd; + import std.utf; + auto cspath = toUTFz!(const(char)*)(from.toNativeString()); + auto cdpath = toUTFz!(const(char)*)(to.toNativeString()); + stat_t st; + enforce(stat(cspath, &st) == 0, "Failed to get attributes of source file."); + if (chown(cdpath, st.st_uid, st.st_gid) != 0) + st.st_mode &= ~(S_ISUID | S_ISGID); + chmod(cdpath, st.st_mode); + } } } /// ditto void copyFile(string from, string to) { - copyFile(Path(from), Path(to)); + copyFile(NativePath(from), NativePath(to)); } version (Windows) extern(Windows) int CreateHardLinkW(in wchar* to, in wchar* from, void* attr=null); // guess whether 2 files are identical, ignores filename and content -private bool sameFile(Path a, Path b) +private bool sameFile(NativePath a, NativePath b) { - static assert(__traits(allMembers, FileInfo)[0] == "name"); - return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $]; + version (Posix) { + auto st_a = std.file.DirEntry(a.toNativeString).statBuf; + auto st_b = std.file.DirEntry(b.toNativeString).statBuf; + return st_a == st_b; + } else { + static assert(__traits(allMembers, FileInfo)[0] == "name"); + return getFileInfo(a).tupleof[1 .. $] == getFileInfo(b).tupleof[1 .. $]; + } } /** Creates a hardlink. */ -void hardLinkFile(Path from, Path to, bool overwrite = false) +void hardLinkFile(NativePath from, NativePath to, bool overwrite = false) { if (existsFile(to)) { enforce(overwrite, "Destination file already exists."); if (auto fe = collectException!FileException(removeFile(to))) { - version (Windows) if (sameFile(from, to)) return; + if (sameFile(from, to)) return; throw fe; } } @@ -176,7 +189,7 @@ /** Removes a file */ -void removeFile(Path path) +void removeFile(NativePath path) { removeFile(path.toNativeString()); } @@ -188,7 +201,7 @@ /** Checks if a file exists */ -bool existsFile(Path path) { +bool existsFile(NativePath path) { return existsFile(path.toNativeString()); } /// ditto @@ -201,36 +214,34 @@ Returns false if the file does not exist. */ -FileInfo getFileInfo(Path path) +FileInfo getFileInfo(NativePath path) { - static if (__VERSION__ >= 2064) - auto ent = std.file.DirEntry(path.toNativeString()); - else auto ent = std.file.dirEntry(path.toNativeString()); + auto ent = std.file.DirEntry(path.toNativeString()); return makeFileInfo(ent); } /// ditto FileInfo getFileInfo(string path) { - return getFileInfo(Path(path)); + return getFileInfo(NativePath(path)); } /** Creates a new directory. */ -void createDirectory(Path path) +void createDirectory(NativePath path) { mkdir(path.toNativeString()); } /// ditto void createDirectory(string path) { - createDirectory(Path(path)); + createDirectory(NativePath(path)); } /** Enumerates all files in the specified directory. */ -void listDirectory(Path path, scope bool delegate(FileInfo info) del) +void listDirectory(NativePath path, scope bool delegate(FileInfo info) del) { foreach( DirEntry ent; dirEntries(path.toNativeString(), SpanMode.shallow) ) if( !del(makeFileInfo(ent)) ) @@ -239,10 +250,10 @@ /// ditto void listDirectory(string path, scope bool delegate(FileInfo info) del) { - listDirectory(Path(path), del); + listDirectory(NativePath(path), del); } /// ditto -int delegate(scope int delegate(ref FileInfo)) iterateDirectory(Path path) +int delegate(scope int delegate(ref FileInfo)) iterateDirectory(NativePath path) { int iterator(scope int delegate(ref FileInfo) del){ int ret = 0; @@ -257,16 +268,16 @@ /// ditto int delegate(scope int delegate(ref FileInfo)) iterateDirectory(string path) { - return iterateDirectory(Path(path)); + return iterateDirectory(NativePath(path)); } /** Returns the current working directory. */ -Path getWorkingDirectory() +NativePath getWorkingDirectory() { - return Path(std.file.getcwd()); + return NativePath(std.file.getcwd()); } diff --git a/source/dub/internal/vibecompat/core/log.d b/source/dub/internal/vibecompat/core/log.d index c0b52f3..e48569e 100644 --- a/source/dub/internal/vibecompat/core/log.d +++ b/source/dub/internal/vibecompat/core/log.d @@ -66,15 +66,15 @@ txt.reserve(256); formattedWrite(txt, fmt, args); - auto threadid = cast(ulong)cast(void*)Thread.getThis(); - auto fiberid = cast(ulong)cast(void*)Fiber.getThis(); + auto threadid = () @trusted { return cast(ulong)cast(void*)Thread.getThis(); } (); + auto fiberid = () @trusted { return cast(ulong)cast(void*)Fiber.getThis(); } (); threadid ^= threadid >> 32; fiberid ^= fiberid >> 32; if (level >= s_minLevel) { File output; - if (level == LogLevel.info) output = stdout; - else output = stderr; + if (level == LogLevel.info) () @trusted { output = stdout; } (); + else () @trusted { output = stderr; } (); if (output.isOpen) { output.writeln(txt.data); output.flush(); diff --git a/source/dub/internal/vibecompat/data/json.d b/source/dub/internal/vibecompat/data/json.d index 0079bb9..0000184 100644 --- a/source/dub/internal/vibecompat/data/json.d +++ b/source/dub/internal/vibecompat/data/json.d @@ -11,7 +11,7 @@ */ module dub.internal.vibecompat.data.json; -version (Have_vibe_d) public import vibe.data.json; +version (Have_vibe_d_data) public import vibe.data.json; else: import dub.internal.vibecompat.data.utils; @@ -136,7 +136,7 @@ /** Allows assignment of D values to a JSON value. */ - ref Json opAssign(Json v) + ref Json opAssign(Json v) return { m_type = v.m_type; final switch(m_type){ @@ -265,7 +265,7 @@ m_object[key] = Json.init; assert(m_object !is null); assert(key in m_object, "Failed to insert key '"~key~"' into AA!?"); - m_object[key].m_type = Type.undefined; // DMDBUG: AAs are teh $H1T!!!11 + m_object[key].m_type = Type.undefined; // DMDBUG: AAs are the $H1T!!!11 assert(m_object[key].type == Type.undefined); m_object[key].m_string = key; version (VibeJsonFieldNames) m_object[key].m_name = format("%s.%s", m_name, key); @@ -892,12 +892,12 @@ case '0': .. case '9': case '-': bool is_float; - auto num = skipNumber(range, is_float); + auto num = skipNumber(range, is_float, filename, line); if( is_float ) ret = to!double(num); else ret = to!long(num); break; case '\"': - ret = skipJsonString(range); + ret = skipJsonString(range, filename, line); break; case '[': Json[] arr; @@ -924,7 +924,7 @@ skipWhitespace(range, line); enforceJson(!range.empty, "Missing '}' before EOF.", filename, line); if(range.front == '}') break; - string key = skipJsonString(range); + string key = skipJsonString(range, filename, line); skipWhitespace(range, line); enforceJson(range.startsWith(":"), "Expected ':' for key '" ~ key ~ "'", filename, line); range.popFront(); @@ -1647,7 +1647,7 @@ m_range.skipWhitespace(&m_line); } else first = false; - auto name = m_range.skipJsonString(&m_line); + auto name = m_range.skipJsonString(null, &m_line); m_range.skipWhitespace(&m_line); enforceJson(!m_range.empty && m_range.front == ':', "Expecting ':', not '"~m_range.front.to!string~"'."); @@ -1692,7 +1692,7 @@ return ret; } else static if (is(T : long)) { bool is_float; - auto num = m_range.skipNumber(is_float); + auto num = m_range.skipNumber(is_float, null, &m_line); enforceJson(!is_float, "Expecting integer number."); return to!T(num); } else static if (is(T : real)) { @@ -1700,7 +1700,7 @@ auto num = m_range.skipNumber(is_float); return to!T(num); } - else static if (is(T == string)) return m_range.skipJsonString(&m_line); + else static if (is(T == string)) return m_range.skipJsonString(null, &m_line); else static if (is(T == Json)) return m_range.parseJson(&m_line); else static if (isJsonSerializable!T) return T.fromJson(m_range.parseJson(&m_line)); else static assert(false, "Unsupported type: " ~ T.stringof); @@ -1743,12 +1743,12 @@ case Json.Type.null_: dst.put("null"); break; case Json.Type.bool_: dst.put(cast(bool)json ? "true" : "false"); break; case Json.Type.int_: formattedWrite(dst, "%d", json.get!long); break; - case Json.Type.float_: + case Json.Type.float_: auto d = json.get!double; - if (d != d) + if (d != d) dst.put("undefined"); // JSON has no NaN value so set null else - formattedWrite(dst, "%.16g", json.get!double); + formattedWrite(dst, "%.16g", json.get!double); break; case Json.Type.string: dst.put('\"'); @@ -1779,24 +1779,38 @@ case Json.Type.object: dst.put('{'); bool first = true; - foreach( string k, ref const Json e; json ){ - if( e.type == Json.Type.undefined ) continue; - if( !first ) dst.put(','); - first = false; - static if (pretty) { + + static if (pretty) { + import std.algorithm.sorting : sort; + string[] keyOrder; + foreach (string key, ref const Json e; json) keyOrder ~= key; + keyOrder.sort(); + + foreach( key; keyOrder ){ + if( json[key].type == Json.Type.undefined ) continue; + if( !first ) dst.put(','); + first = false; dst.put('\n'); foreach (tab; 0 .. level+1) dst.put('\t'); + dst.put('\"'); + jsonEscape(dst, key); + dst.put(pretty ? `": ` : `":`); + writeJsonString!(R, pretty)(dst, json[key], level+1); } - dst.put('\"'); - jsonEscape(dst, k); - dst.put(pretty ? `": ` : `":`); - writeJsonString!(R, pretty)(dst, e, level+1); - } - static if (pretty) { if (json.length > 0) { dst.put('\n'); foreach (tab; 0 .. level) dst.put('\t'); } + } else { + foreach( string k, ref const Json e; json ){ + if( e.type == Json.Type.undefined ) continue; + if( !first ) dst.put(','); + first = false; + dst.put('\"'); + jsonEscape(dst, k); + dst.put(pretty ? `": ` : `":`); + writeJsonString!(R, pretty)(dst, e, level+1); + } } dst.put('}'); break; @@ -1818,13 +1832,6 @@ 1, {} ] -}` || a.toPrettyString() == -`{ - "b": [ - 1, - {} - ], - "a": [] }`); } @@ -1915,7 +1922,7 @@ char[13] buf; int len; dchar codepoint = decode(s, pos); - import std.c.stdio : sprintf; + import core.stdc.stdio : sprintf; /* codepoint is in BMP */ if(codepoint < 0x10000) { @@ -1955,7 +1962,7 @@ } /// private -private string jsonUnescape(R)(ref R range) +private string jsonUnescape(R)(ref R range, string filename, int* line) { auto ret = appender!string(); while(!range.empty){ @@ -1964,9 +1971,9 @@ case '"': return ret.data; case '\\': range.popFront(); - enforceJson(!range.empty, "Unterminated string escape sequence."); + enforceJson(!range.empty, "Unterminated string escape sequence.", filename, line); switch(range.front){ - default: enforceJson(false, "Invalid string escape sequence."); break; + default: enforceJson(false, "Invalid string escape sequence.", filename, line); break; case '"': ret.put('\"'); range.popFront(); break; case '\\': ret.put('\\'); range.popFront(); break; case '/': ret.put('/'); range.popFront(); break; @@ -1983,14 +1990,14 @@ dchar uch = 0; foreach( i; 0 .. 4 ){ uch *= 16; - enforceJson(!range.empty, "Unicode sequence must be '\\uXXXX'."); + enforceJson(!range.empty, "Unicode sequence must be '\\uXXXX'.", filename, line); auto dc = range.front; range.popFront(); if( dc >= '0' && dc <= '9' ) uch += dc - '0'; else if( dc >= 'a' && dc <= 'f' ) uch += dc - 'a' + 10; else if( dc >= 'A' && dc <= 'F' ) uch += dc - 'A' + 10; - else enforceJson(false, "Unicode sequence must be '\\uXXXX'."); + else enforceJson(false, "Unicode sequence must be '\\uXXXX'.", filename, line); } return uch; } @@ -2001,7 +2008,7 @@ /* surrogate pair */ range.popFront(); // backslash '\' auto uch2 = decode_unicode_escape(); - enforceJson(0xDC00 <= uch2 && uch2 <= 0xDFFF, "invalid Unicode"); + enforceJson(0xDC00 <= uch2 && uch2 <= 0xDFFF, "invalid Unicode", filename, line); { /* valid second surrogate */ uch = @@ -2024,7 +2031,7 @@ } /// private -private string skipNumber(R)(ref R s, out bool is_float) +private string skipNumber(R)(ref R s, out bool is_float, string filename, int* line) { // TODO: make this work with input ranges size_t idx = 0; @@ -2032,7 +2039,7 @@ if (s[idx] == '-') idx++; if (s[idx] == '0') idx++; else { - enforceJson(isDigit(s[idx++]), "Digit expected at beginning of number."); + enforceJson(isDigit(s[idx++]), "Digit expected at beginning of number.", filename, line); while( idx < s.length && isDigit(s[idx]) ) idx++; } @@ -2046,7 +2053,7 @@ idx++; is_float = true; if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++; - enforceJson( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]); + enforceJson( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx], filename, line); idx++; while( idx < s.length && isDigit(s[idx]) ) idx++; } @@ -2057,13 +2064,13 @@ } /// private -private string skipJsonString(R)(ref R s, int* line = null) +private string skipJsonString(R)(ref R s, string filename, int* line) { // TODO: count or disallow any newlines inside of the string - enforceJson(!s.empty && s.front == '"', "Expected '\"' to start string."); + enforceJson(!s.empty && s.front == '"', "Expected '\"' to start string.", filename, line); s.popFront(); - string ret = jsonUnescape(s); - enforceJson(!s.empty && s.front == '"', "Expected '\"' to terminate string."); + string ret = jsonUnescape(s, filename, line); + enforceJson(!s.empty && s.front == '"', "Expected '\"' to terminate string.", filename, line); s.popFront(); return ret; } @@ -2102,15 +2109,19 @@ private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message = "JSON exception") { - static if (__VERSION__ >= 2065) enforceEx!JSONException(cond, message, file, line); - else if (!cond) throw new JSONException(message); + static if (__VERSION__ >= 2079) + enforce!JSONException(cond, message, file, line); + else + enforceEx!JSONException(cond, message, file, line); } private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int err_line) { auto errmsg() { return format("%s(%s): Error: %s", err_file, err_line+1, message); } - static if (__VERSION__ >= 2065) enforceEx!JSONException(cond, errmsg, file, line); - else if (!cond) throw new JSONException(errmsg); + static if (__VERSION__ >= 2079) + enforce!JSONException(cond, errmsg, file, line); + else + enforceEx!JSONException(cond, errmsg, file, line); } private void enforceJson(string file = __FILE__, size_t line = __LINE__)(bool cond, lazy string message, string err_file, int* err_line) diff --git a/source/dub/internal/vibecompat/data/serialization.d b/source/dub/internal/vibecompat/data/serialization.d index 818739e..0f9b7c4 100644 --- a/source/dub/internal/vibecompat/data/serialization.d +++ b/source/dub/internal/vibecompat/data/serialization.d @@ -91,7 +91,7 @@ */ module dub.internal.vibecompat.data.serialization; -version (Have_vibe_d) public import vibe.data.serialization; +version (Have_vibe_d_data) public import vibe.data.serialization; else: import dub.internal.vibecompat.data.utils; @@ -184,7 +184,7 @@ string toRepresentation(T value) { return to!string(value.x) ~ "x" ~ to!string(value.y); } - + T fromRepresentation(string value) { string[] fields = value.split('x'); alias fieldT = typeof(T.x); @@ -196,7 +196,7 @@ } /// -static if (__VERSION__ >= 2065) unittest { +unittest { import dub.internal.vibecompat.data.json; static struct SizeI { @@ -266,9 +266,9 @@ } /// -static if (__VERSION__ >= 2065) unittest { +unittest { import dub.internal.vibecompat.data.json; - + static struct SizeI { int x; int y; @@ -278,7 +278,7 @@ SizeI sizeI = deserializeWithPolicy!(JsonSerializer, SizePol, SizeI)(serializedI); assert(sizeI.x == 1); assert(sizeI.y == 2); - + static struct SizeF { float x; float y; @@ -292,7 +292,7 @@ private void serializeImpl(Serializer, alias Policy, T, ATTRIBUTES...)(ref Serializer serializer, T value) { import std.typecons : Nullable, Tuple, tuple; - static if (__VERSION__ >= 2067) import std.typecons : BitFlags; + import std.typecons : BitFlags; static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); @@ -356,7 +356,7 @@ } else static if (/*isInstanceOf!(Nullable, TU)*/is(T == Nullable!TPS, TPS...)) { if (value.isNull()) serializeImpl!(Serializer, Policy, typeof(null))(serializer, null); else serializeImpl!(Serializer, Policy, typeof(value.get()), ATTRIBUTES)(serializer, value.get()); - } else static if (__VERSION__ >= 2067 && is(T == BitFlags!E, E)) { + } else static if (is(T == BitFlags!E, E)) { size_t cnt = 0; foreach (v; EnumMembers!E) if (value & v) @@ -450,7 +450,7 @@ private T deserializeImpl(T, alias Policy, Serializer, ATTRIBUTES...)(ref Serializer deserializer) { import std.typecons : Nullable; - static if (__VERSION__ >= 2067) import std.typecons : BitFlags; + import std.typecons : BitFlags; static assert(Serializer.isSupportedValueType!string, "All serializers must support string values."); static assert(Serializer.isSupportedValueType!(typeof(null)), "All serializers must support null values."); @@ -496,7 +496,7 @@ } else static if (isInstanceOf!(Nullable, T)) { if (deserializer.tryReadNull()) return T.init; return T(deserializeImpl!(typeof(T.init.get()), Policy, Serializer, ATTRIBUTES)(deserializer)); - } else static if (__VERSION__ >= 2067 && is(T == BitFlags!E, E)) { + } else static if (is(T == BitFlags!E, E)) { T ret; deserializer.readArray!(E[])((sz) {}, { ret |= deserializeImpl!(E, Policy, Serializer, ATTRIBUTES)(deserializer); @@ -825,22 +825,22 @@ */ template isPolicySerializable(alias Policy, T) { - enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && + enum bool isPolicySerializable = is(typeof(Policy!T.toRepresentation(T.init))) && is(typeof(Policy!T.fromRepresentation(Policy!T.toRepresentation(T.init))) == T); } /// unittest { import std.conv; - + // represented as a string when serialized static struct S { int value; - + // dummy example implementations string toString() const { return value.to!string(); } static S fromString(string s) { return S(s.to!int()); } } - + static assert(isStringSerializable!S); } @@ -865,7 +865,7 @@ /// unittest { import std.conv; - + // To be represented as the boxed value when serialized static struct Box(T) { T value; @@ -883,7 +883,7 @@ auto toRepresentation(S s) { return s.value; } - + S fromRepresentation(typeof(toRepresentation(S.init)) v) { return S(v); } @@ -893,7 +893,7 @@ auto toRepresentation(S s) { return s.get(); } - + S fromRepresentation(typeof(toRepresentation(S.init)) v) { S s; s.get() = v; @@ -1125,7 +1125,8 @@ test(12.0f, "V(f)(12)"); assert(serialize!TestSerializer(null) == "null"); test(["hello", "world"], "A(AAya)[2][AE(Aya,0)(V(Aya)(hello))AE(Aya,0)AE(Aya,1)(V(Aya)(world))AE(Aya,1)]A(AAya)"); - test(["hello": "world"], "D(HAyaAya){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(HAyaAya)"); + string mangleOfAA = (string[string]).mangleof; + test(["hello": "world"], "D(" ~ mangleOfAA ~ "){DE(Aya,hello)(V(Aya)(world))DE(Aya,hello)}D(" ~ mangleOfAA ~ ")"); test(cast(int*)null, "null"); int i = 42; test(&i, "V(i)(42)"); @@ -1275,7 +1276,6 @@ assert(s.serializeToJson().deserializeJson!S() == s); } -static if (__VERSION__ >= 2067) unittest { // test BitFlags serialization import std.typecons : BitFlags; diff --git a/source/dub/internal/vibecompat/data/utils.d b/source/dub/internal/vibecompat/data/utils.d index d41cd4d..a967455 100644 --- a/source/dub/internal/vibecompat/data/utils.d +++ b/source/dub/internal/vibecompat/data/utils.d @@ -7,7 +7,7 @@ */ module dub.internal.vibecompat.data.utils; -version (Have_vibe_d) {} +version (Have_vibe_d_data) {} else: public import std.traits; @@ -374,10 +374,10 @@ /** TypeTuple which does not auto-expand. - + Useful when you need to multiple several type tuples as different template argument - list parameters, without merging those. + list parameters, without merging those. */ template Group(T...) { @@ -407,10 +407,10 @@ version (unittest) // NOTE: GDC complains about template definitions in unittest blocks { import std.typetuple; - + alias group = Group!(int, double, string); alias group2 = Group!(); - + template Fake(T...) { int[] expand; diff --git a/source/dub/internal/vibecompat/inet/path.d b/source/dub/internal/vibecompat/inet/path.d index d42b005..047ac5d 100644 --- a/source/dub/internal/vibecompat/inet/path.d +++ b/source/dub/internal/vibecompat/inet/path.d @@ -7,7 +7,8 @@ */ module dub.internal.vibecompat.inet.path; -version (Have_vibe_d) public import vibe.inet.path; +version (Have_vibe_core) public import vibe.core.path; +else version (Have_vibe_d_core) public import vibe.inet.path; else: import std.algorithm; @@ -17,6 +18,9 @@ import std.string; +deprecated("Use NativePath instead.") +alias Path = NativePath; + /** Represents an absolute or relative file system path. @@ -24,18 +28,21 @@ are done to disallow invalid operations such as concatenating two absolute paths. It also validates path strings and allows for easy checking of malicious relative paths. */ -struct Path { +struct NativePath { private { immutable(PathEntry)[] m_nodes; bool m_absolute = false; bool m_endsWithSlash = false; } - /// Constructs a Path object by parsing a path string. + alias Segment = PathEntry; + + alias bySegment = nodes; + + /// Constructs a NativePath object by parsing a path string. this(string pathstr) { - static if (__VERSION__ < 2066) m_nodes = splitPath(pathstr).idup; - else m_nodes = splitPath(pathstr); + m_nodes = splitPath(pathstr); m_absolute = (pathstr.startsWith("/") || m_nodes.length > 0 && (m_nodes[0].toString().countUntil(':')>0 || m_nodes[0] == "\\")); m_endsWithSlash = pathstr.endsWith("/"); } @@ -109,7 +116,7 @@ return ret.data; } - /// Converts the Path object to a native path string (backslash as path separator on Windows). + /// Converts the NativePath object to a native path string (backslash as path separator on Windows). string toNativeString() const { if (m_nodes.empty) { @@ -140,7 +147,7 @@ } /// Tests if `rhs` is an anchestor or the same as this path. - bool startsWith(const Path rhs) const { + bool startsWith(const NativePath rhs) const { if( rhs.m_nodes.length > m_nodes.length ) return false; foreach( i; 0 .. rhs.m_nodes.length ) if( m_nodes[i] != rhs.m_nodes[i] ) @@ -149,7 +156,7 @@ } /// Computes the relative path from `parentPath` to this path. - Path relativeTo(const Path parentPath) const { + NativePath relativeTo(const NativePath parentPath) const { assert(this.absolute && parentPath.absolute, "Determining relative path between non-absolute paths."); version(Windows){ // a path such as ..\C:\windows is not valid, so force the path to stay absolute in this case @@ -165,20 +172,20 @@ nup++; } assert(m_nodes.length >= parentPath.length - nup); - Path ret = Path(null, false); + NativePath ret = NativePath(null, false); assert(m_nodes.length >= parentPath.length - nup); ret.m_endsWithSlash = true; foreach( i; 0 .. nup ) ret ~= ".."; - ret ~= Path(m_nodes[parentPath.length-nup .. $], false); + ret ~= NativePath(m_nodes[parentPath.length-nup .. $], false); ret.m_endsWithSlash = this.m_endsWithSlash; return ret; } /// The last entry of the path - @property ref immutable(PathEntry) head() const { enforce(m_nodes.length > 0); return m_nodes[$-1]; } + @property ref immutable(PathEntry) head() const { enforce(m_nodes.length > 0, "Getting head of empty path."); return m_nodes[$-1]; } /// The parent path - @property Path parentPath() const { return this[0 .. length-1]; } + @property NativePath parentPath() const { return this[0 .. length-1]; } /// The ist of path entries of which this path is composed @property immutable(PathEntry)[] nodes() const { return m_nodes; } @@ -198,16 +205,16 @@ @property bool external() const { return !m_absolute && m_nodes.length > 0 && m_nodes[0].m_name == ".."; } ref immutable(PathEntry) opIndex(size_t idx) const { return m_nodes[idx]; } - Path opSlice(size_t start, size_t end) const { - auto ret = Path(m_nodes[start .. end], start == 0 ? absolute : false); + NativePath opSlice(size_t start, size_t end) const { + auto ret = NativePath(m_nodes[start .. end], start == 0 ? absolute : false); if( end == m_nodes.length ) ret.m_endsWithSlash = m_endsWithSlash; return ret; } size_t opDollar(int dim)() const if(dim == 0) { return m_nodes.length; } - Path opBinary(string OP)(const Path rhs) const if( OP == "~" ) { - Path ret; + NativePath opBinary(string OP)(const NativePath rhs) const if( OP == "~" ) { + NativePath ret; ret.m_nodes = m_nodes; ret.m_absolute = m_absolute; ret.m_endsWithSlash = rhs.m_endsWithSlash; @@ -230,14 +237,14 @@ return ret; } - Path opBinary(string OP)(string rhs) const if( OP == "~" ) { assert(rhs.length > 0, "Cannot append empty path string."); return opBinary!"~"(Path(rhs)); } - Path opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { assert(rhs.toString().length > 0, "Cannot append empty path string."); return opBinary!"~"(Path(rhs)); } - void opOpAssign(string OP)(string rhs) if( OP == "~" ) { assert(rhs.length > 0, "Cannot append empty path string."); opOpAssign!"~"(Path(rhs)); } - void opOpAssign(string OP)(PathEntry rhs) if( OP == "~" ) { assert(rhs.toString().length > 0, "Cannot append empty path string."); opOpAssign!"~"(Path(rhs)); } - void opOpAssign(string OP)(Path rhs) if( OP == "~" ) { auto p = this ~ rhs; m_nodes = p.m_nodes; m_endsWithSlash = rhs.m_endsWithSlash; } + NativePath opBinary(string OP)(string rhs) const if( OP == "~" ) { assert(rhs.length > 0, "Cannot append empty path string."); return opBinary!"~"(NativePath(rhs)); } + NativePath opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { assert(rhs.toString().length > 0, "Cannot append empty path string."); return opBinary!"~"(NativePath(rhs)); } + void opOpAssign(string OP)(string rhs) if( OP == "~" ) { assert(rhs.length > 0, "Cannot append empty path string."); opOpAssign!"~"(NativePath(rhs)); } + void opOpAssign(string OP)(PathEntry rhs) if( OP == "~" ) { assert(rhs.toString().length > 0, "Cannot append empty path string."); opOpAssign!"~"(NativePath(rhs)); } + void opOpAssign(string OP)(NativePath rhs) if( OP == "~" ) { auto p = this ~ rhs; m_nodes = p.m_nodes; m_endsWithSlash = rhs.m_endsWithSlash; } /// Tests two paths for equality using '=='. - bool opEquals(ref const Path rhs) const { + bool opEquals(ref const NativePath rhs) const { if( m_absolute != rhs.m_absolute ) return false; if( m_endsWithSlash != rhs.m_endsWithSlash ) return false; if( m_nodes.length != rhs.length ) return false; @@ -247,9 +254,9 @@ return true; } /// ditto - bool opEquals(const Path other) const { return opEquals(other); } + bool opEquals(const NativePath other) const { return opEquals(other); } - int opCmp(ref const Path rhs) const { + int opCmp(ref const NativePath rhs) const { if( m_absolute != rhs.m_absolute ) return cast(int)m_absolute - cast(int)rhs.m_absolute; foreach( i; 0 .. min(m_nodes.length, rhs.m_nodes.length) ) if( m_nodes[i] != rhs.m_nodes[i] ) @@ -284,7 +291,9 @@ string toString() const pure { return m_name; } - Path opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { return Path([this, rhs], false); } + @property string name() const { return m_name; } + + NativePath opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { return NativePath([this, rhs], false); } bool opEquals(ref const PathEntry rhs) const { return m_name == rhs.m_name; } bool opEquals(PathEntry rhs) const { return m_name == rhs.m_name; } @@ -303,8 +312,8 @@ /// Joins two path strings. subpath must be relative. string joinPath(string basepath, string subpath) { - Path p1 = Path(basepath); - Path p2 = Path(subpath); + NativePath p1 = NativePath(basepath); + NativePath p2 = NativePath(subpath); return (p1 ~ p2).toString(); } @@ -347,13 +356,13 @@ unittest { - Path p; + NativePath p; assert(p.toNativeString() == "."); p.endsWithSlash = true; version(Windows) assert(p.toNativeString() == ".\\"); else assert(p.toNativeString() == "./"); - p = Path("test/"); + p = NativePath("test/"); version(Windows) assert(p.toNativeString() == "test\\"); else assert(p.toNativeString() == "test/"); p.endsWithSlash = false; @@ -364,7 +373,7 @@ { { auto unc = "\\\\server\\share\\path"; - auto uncp = Path(unc); + auto uncp = NativePath(unc); uncp.normalize(); version(Windows) assert(uncp.toNativeString() == unc); assert(uncp.absolute); @@ -373,7 +382,7 @@ { auto abspath = "/test/path/"; - auto abspathp = Path(abspath); + auto abspathp = NativePath(abspath); assert(abspathp.toString() == abspath); version(Windows) {} else assert(abspathp.toNativeString() == abspath); assert(abspathp.absolute); @@ -385,7 +394,7 @@ { auto relpath = "test/path/"; - auto relpathp = Path(relpath); + auto relpathp = NativePath(relpath); assert(relpathp.toString() == relpath); version(Windows) assert(relpathp.toNativeString() == "test\\path\\"); else assert(relpathp.toNativeString() == relpath); @@ -398,7 +407,7 @@ { auto winpath = "C:\\windows\\test"; - auto winpathp = Path(winpath); + auto winpathp = NativePath(winpath); version(Windows) { assert(winpathp.toString() == "C:/windows/test", winpathp.toString()); assert(winpathp.toNativeString() == winpath); @@ -416,7 +425,7 @@ { auto dotpath = "/test/../test2/././x/y"; - auto dotpathp = Path(dotpath); + auto dotpathp = NativePath(dotpath); assert(dotpathp.toString() == "/test/../test2/././x/y"); dotpathp.normalize(); assert(dotpathp.toString() == "/test2/x/y"); @@ -424,7 +433,7 @@ { auto dotpath = "/test/..////test2//./x/y"; - auto dotpathp = Path(dotpath); + auto dotpathp = NativePath(dotpath); assert(dotpathp.toString() == "/test/..////test2//./x/y"); dotpathp.normalize(); assert(dotpathp.toString() == "/test2/x/y"); @@ -432,46 +441,46 @@ { auto parentpath = "/path/to/parent"; - auto parentpathp = Path(parentpath); + auto parentpathp = NativePath(parentpath); auto subpath = "/path/to/parent/sub/"; - auto subpathp = Path(subpath); + auto subpathp = NativePath(subpath); auto subpath_rel = "sub/"; assert(subpathp.relativeTo(parentpathp).toString() == subpath_rel); auto subfile = "/path/to/parent/child"; - auto subfilep = Path(subfile); + auto subfilep = NativePath(subfile); auto subfile_rel = "child"; assert(subfilep.relativeTo(parentpathp).toString() == subfile_rel); } { // relative paths across Windows devices are not allowed version (Windows) { - auto p1 = Path("\\\\server\\share"); assert(p1.absolute); - auto p2 = Path("\\\\server\\othershare"); assert(p2.absolute); - auto p3 = Path("\\\\otherserver\\share"); assert(p3.absolute); - auto p4 = Path("C:\\somepath"); assert(p4.absolute); - auto p5 = Path("C:\\someotherpath"); assert(p5.absolute); - auto p6 = Path("D:\\somepath"); assert(p6.absolute); - assert(p4.relativeTo(p5) == Path("../somepath")); - assert(p4.relativeTo(p6) == Path("C:\\somepath")); - assert(p4.relativeTo(p1) == Path("C:\\somepath")); - assert(p1.relativeTo(p2) == Path("../share")); - assert(p1.relativeTo(p3) == Path("\\\\server\\share")); - assert(p1.relativeTo(p4) == Path("\\\\server\\share")); + auto p1 = NativePath("\\\\server\\share"); assert(p1.absolute); + auto p2 = NativePath("\\\\server\\othershare"); assert(p2.absolute); + auto p3 = NativePath("\\\\otherserver\\share"); assert(p3.absolute); + auto p4 = NativePath("C:\\somepath"); assert(p4.absolute); + auto p5 = NativePath("C:\\someotherpath"); assert(p5.absolute); + auto p6 = NativePath("D:\\somepath"); assert(p6.absolute); + assert(p4.relativeTo(p5) == NativePath("../somepath")); + assert(p4.relativeTo(p6) == NativePath("C:\\somepath")); + assert(p4.relativeTo(p1) == NativePath("C:\\somepath")); + assert(p1.relativeTo(p2) == NativePath("../share")); + assert(p1.relativeTo(p3) == NativePath("\\\\server\\share")); + assert(p1.relativeTo(p4) == NativePath("\\\\server\\share")); } } } unittest { - assert(Path("/foo/bar/baz").relativeTo(Path("/foo")).toString == "bar/baz"); - assert(Path("/foo/bar/baz/").relativeTo(Path("/foo")).toString == "bar/baz/"); - assert(Path("/foo/bar").relativeTo(Path("/foo")).toString == "bar"); - assert(Path("/foo/bar/").relativeTo(Path("/foo")).toString == "bar/"); - assert(Path("/foo").relativeTo(Path("/foo/bar")).toString() == ".."); - assert(Path("/foo/").relativeTo(Path("/foo/bar")).toString() == "../"); - assert(Path("/foo/baz").relativeTo(Path("/foo/bar/baz")).toString() == "../../baz"); - assert(Path("/foo/baz/").relativeTo(Path("/foo/bar/baz")).toString() == "../../baz/"); - assert(Path("/foo/").relativeTo(Path("/foo/bar/baz")).toString() == "../../"); - assert(Path("/foo/").relativeTo(Path("/foo/bar/baz/mumpitz")).toString() == "../../../"); - assert(Path("/foo").relativeTo(Path("/foo")).toString() == ""); - assert(Path("/foo/").relativeTo(Path("/foo")).toString() == ""); + assert(NativePath("/foo/bar/baz").relativeTo(NativePath("/foo")).toString == "bar/baz"); + assert(NativePath("/foo/bar/baz/").relativeTo(NativePath("/foo")).toString == "bar/baz/"); + assert(NativePath("/foo/bar").relativeTo(NativePath("/foo")).toString == "bar"); + assert(NativePath("/foo/bar/").relativeTo(NativePath("/foo")).toString == "bar/"); + assert(NativePath("/foo").relativeTo(NativePath("/foo/bar")).toString() == ".."); + assert(NativePath("/foo/").relativeTo(NativePath("/foo/bar")).toString() == "../"); + assert(NativePath("/foo/baz").relativeTo(NativePath("/foo/bar/baz")).toString() == "../../baz"); + assert(NativePath("/foo/baz/").relativeTo(NativePath("/foo/bar/baz")).toString() == "../../baz/"); + assert(NativePath("/foo/").relativeTo(NativePath("/foo/bar/baz")).toString() == "../../"); + assert(NativePath("/foo/").relativeTo(NativePath("/foo/bar/baz/mumpitz")).toString() == "../../../"); + assert(NativePath("/foo").relativeTo(NativePath("/foo")).toString() == ""); + assert(NativePath("/foo/").relativeTo(NativePath("/foo")).toString() == ""); } diff --git a/source/dub/internal/vibecompat/inet/url.d b/source/dub/internal/vibecompat/inet/url.d index d9f021f..720d5d0 100644 --- a/source/dub/internal/vibecompat/inet/url.d +++ b/source/dub/internal/vibecompat/inet/url.d @@ -9,7 +9,7 @@ public import dub.internal.vibecompat.inet.path; -version (Have_vibe_d) public import vibe.inet.url; +version (Have_vibe_d_core) public import vibe.inet.url; else: import std.algorithm; @@ -18,6 +18,7 @@ import std.exception; import std.string; import std.uri; +import std.meta : AliasSeq; /** @@ -27,17 +28,18 @@ private { string m_schema; string m_pathString; - Path m_path; + NativePath m_path; string m_host; ushort m_port; string m_username; string m_password; string m_queryString; string m_anchor; + alias m_schemes = AliasSeq!("http", "https", "ftp", "spdy", "file", "sftp"); } /// Constructs a new URL object from its components. - this(string schema, string host, ushort port, Path path) + this(string schema, string host, ushort port, NativePath path) { m_schema = schema; m_host = host; @@ -46,7 +48,7 @@ m_pathString = path.toString(); } /// ditto - this(string schema, Path path) + this(string schema, NativePath path) { this(schema, null, 0, path); } @@ -66,48 +68,43 @@ str = str[idx+1 .. $]; bool requires_host = false; - switch(m_schema){ - case "http": - case "https": - case "ftp": - case "spdy": - case "sftp": - case "file": - // proto://server/path style - enforce(str.startsWith("//"), "URL must start with proto://..."); - requires_host = true; - str = str[2 .. $]; - goto default; - default: - auto si = str.countUntil('/'); - if( si < 0 ) si = str.length; - auto ai = str[0 .. si].countUntil('@'); - sizediff_t hs = 0; - if( ai >= 0 ){ - hs = ai+1; - auto ci = str[0 .. ai].countUntil(':'); - if( ci >= 0 ){ - m_username = str[0 .. ci]; - m_password = str[ci+1 .. ai]; - } else m_username = str[0 .. ai]; - enforce(m_username.length > 0, "Empty user name in URL."); - } - - m_host = str[hs .. si]; - auto pi = m_host.countUntil(':'); - if(pi > 0) { - enforce(pi < m_host.length-1, "Empty port in URL."); - m_port = to!ushort(m_host[pi+1..$]); - m_host = m_host[0 .. pi]; - } - - enforce(!requires_host || m_schema == "file" || m_host.length > 0, - "Empty server name in URL."); - str = str[si .. $]; + auto schema_parts = m_schema.split("+"); + if (!schema_parts.empty && schema_parts.back.canFind(m_schemes)) + { + // proto://server/path style + enforce(str.startsWith("//"), "URL must start with proto://..."); + requires_host = true; + str = str[2 .. $]; } + + auto si = str.countUntil('/'); + if( si < 0 ) si = str.length; + auto ai = str[0 .. si].countUntil('@'); + sizediff_t hs = 0; + if( ai >= 0 ){ + hs = ai+1; + auto ci = str[0 .. ai].countUntil(':'); + if( ci >= 0 ){ + m_username = str[0 .. ci]; + m_password = str[ci+1 .. ai]; + } else m_username = str[0 .. ai]; + enforce(m_username.length > 0, "Empty user name in URL."); + } + + m_host = str[hs .. si]; + auto pi = m_host.countUntil(':'); + if(pi > 0) { + enforce(pi < m_host.length-1, "Empty port in URL."); + m_port = to!ushort(m_host[pi+1..$]); + m_host = m_host[0 .. pi]; + } + + enforce(!requires_host || m_schema == "file" || m_host.length > 0, + "Empty server name in URL."); + str = str[si .. $]; } - this.localURI = str; + this.localURI = (str == "") ? "/" : str; } /// ditto static URL parse(string url_string) @@ -124,9 +121,9 @@ @property string pathString() const { return m_pathString; } /// The path part of the URL - @property Path path() const { return m_path; } + @property NativePath path() const { return m_path; } /// ditto - @property void path(Path p) + @property void path(NativePath p) { m_path = p; auto pstr = p.toString(); @@ -193,7 +190,7 @@ } m_pathString = str; - m_path = Path(decode(str)); + m_path = NativePath(decode(str)); } /// The URL to the parent path with query string and anchor stripped. @@ -215,16 +212,10 @@ auto dst = appender!string(); dst.put(schema); dst.put(":"); - switch(schema){ - default: break; - case "file": - case "http": - case "https": - case "ftp": - case "spdy": - case "sftp": - dst.put("//"); - break; + auto schema_parts = schema.split("+"); + if (!schema_parts.empty && schema_parts.back.canFind(m_schemes)) + { + dst.put("//"); } dst.put(host); if( m_port > 0 ) formattedWrite(dst, ":%d", m_port); @@ -239,9 +230,9 @@ return path.startsWith(rhs.m_path); } - URL opBinary(string OP)(Path rhs) const if( OP == "~" ) { return URL(m_schema, m_host, m_port, m_path ~ rhs); } + URL opBinary(string OP)(NativePath rhs) const if( OP == "~" ) { return URL(m_schema, m_host, m_port, m_path ~ rhs); } URL opBinary(string OP)(PathEntry rhs) const if( OP == "~" ) { return URL(m_schema, m_host, m_port, m_path ~ rhs); } - void opOpAssign(string OP)(Path rhs) if( OP == "~" ) { m_path ~= rhs; } + void opOpAssign(string OP)(NativePath rhs) if( OP == "~" ) { m_path ~= rhs; } void opOpAssign(string OP)(PathEntry rhs) if( OP == "~" ) { m_path ~= rhs; } /// Tests two URLs for equality using '=='. @@ -266,7 +257,7 @@ auto url = URL.parse("https://www.example.net/index.html"); assert(url.schema == "https", url.schema); assert(url.host == "www.example.net", url.host); - assert(url.path == Path("/index.html"), url.path.toString()); + assert(url.path == NativePath("/index.html"), url.path.toString()); url = URL.parse("http://jo.doe:password@sub.www.example.net:4711/sub2/index.html?query#anchor"); assert(url.schema == "http", url.schema); @@ -277,4 +268,15 @@ assert(url.path.toString() == "/sub2/index.html", url.path.toString()); assert(url.queryString == "query", url.queryString); assert(url.anchor == "anchor", url.anchor); + + url = URL("http://localhost")~NativePath("packages"); + assert(url.toString() == "http://localhost/packages", url.toString()); + + url = URL("http://localhost/")~NativePath("packages"); + assert(url.toString() == "http://localhost/packages", url.toString()); + + url = URL.parse("dub+https://code.dlang.org/"); + assert(url.host == "code.dlang.org"); + assert(url.toString() == "dub+https://code.dlang.org/"); + assert(url.schema == "dub+https"); } diff --git a/source/dub/package_.d b/source/dub/package_.d index 08db442..88e3765 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -60,8 +60,8 @@ */ class Package { private { - Path m_path; - Path m_infoFile; + NativePath m_path; + NativePath m_infoFile; PackageRecipe m_info; PackageRecipe m_rawRecipe; Package m_parentPackage; @@ -79,7 +79,7 @@ instead of the one declared in the package recipe, or the one determined by invoking the VCS (GIT currently). */ - this(Json json_recipe, Path root = Path(), Package parent = null, string version_override = "") + this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") { import dub.recipe.json; @@ -88,7 +88,7 @@ this(recipe, root, parent, version_override); } /// ditto - this(PackageRecipe recipe, Path root = Path(), Package parent = null, string version_override = "") + this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") { // save the original recipe m_rawRecipe = recipe.clone; @@ -116,8 +116,8 @@ // use the given recipe as the basis m_info = recipe; + checkDubRequirements(); fillWithDefaults(); - simpleLint(); } /** Searches the given directory for package recipe files. @@ -129,13 +129,13 @@ Returns the full path to the package file, if any was found. Otherwise returns an empty path. */ - static Path findPackageFile(Path directory) + static NativePath findPackageFile(NativePath directory) { foreach (file; packageInfoFiles) { auto filename = directory ~ file.filename; if (existsFile(filename)) return filename; } - return Path.init; + return NativePath.init; } /** Constructs a `Package` using a package that is physically present on the local file system. @@ -150,13 +150,13 @@ instead of the one declared in the package recipe, or the one determined by invoking the VCS (GIT currently). */ - static Package load(Path root, Path recipe_file = Path.init, Package parent = null, string version_override = "") + static Package load(NativePath root, NativePath recipe_file = NativePath.init, Package parent = null, string version_override = "") { import dub.recipe.io; if (recipe_file.empty) recipe_file = findPackageFile(root); - enforce(!recipe_file.empty, + enforce(!recipe_file.empty, "No package file found in %s, expected one of %s" .format(root.toNativeString(), packageInfoFiles.map!(f => cast(string)f.filename).join("/"))); @@ -184,7 +184,7 @@ Note that this can be empty for packages that are not stored in the local file system. */ - @property Path path() const { return m_path; } + @property NativePath path() const { return m_path; } /** Accesses the version associated with this package. @@ -219,7 +219,7 @@ Note that this can be empty for packages that are not stored in the local file system. */ - @property Path recipePath() const { return m_infoFile; } + @property NativePath recipePath() const { return m_infoFile; } /** Returns the base package of this package. @@ -266,7 +266,7 @@ m_infoFile = m_path ~ defaultPackageFilename; } /// ditto - void storeInfo(Path path) + void storeInfo(NativePath path) const { enforce(!version_.isUnknown, "Trying to store a package with an 'unknown' version, this is not supported."); auto filename = path ~ defaultPackageFilename; @@ -275,6 +275,22 @@ dstFile.writePrettyJsonString(m_info.toJson()); } + /// Get the metadata cache for this package + @property Json metadataCache() + { + enum silent_fail = true; + return jsonFromFile(m_path ~ ".dub/metadata_cache.json", silent_fail); + } + + /// Write metadata cache for this package + @property void metadataCache(Json json) + { + enum create_if_missing = true; + if (isWritableDir(m_path ~ ".dub", create_if_missing)) + writeJsonFile(m_path ~ ".dub/metadata_cache.json", json); + // TODO: store elsewhere + } + /** Returns the package recipe of a non-path-based sub package. For sub packages that are declared within the package recipe of the @@ -410,6 +426,7 @@ case "profile-gc": settings.addOptions(profileGC, debugInfo); break; case "cov": settings.addOptions(coverage, debugInfo); break; case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break; + case "syntax": settings.addOptions(syntaxOnly); break; } } } @@ -500,7 +517,7 @@ This includes dependencies that are declared at the root level of the package recipe, as well as those declared within the specified configuration. If no configuration with the given name exists, only - dependencies declared at the root level will be retunred. + dependencies declared at the root level will be returned. See_Also: `hasDependency` */ @@ -527,14 +544,22 @@ PackageDependency[] getAllDependencies() const { auto ret = appender!(PackageDependency[]); - foreach (n, d; this.recipe.buildSettings.dependencies) - ret ~= PackageDependency(n, d); - foreach (ref c; this.recipe.configurations) - foreach (n, d; c.buildSettings.dependencies) - ret ~= PackageDependency(n, d); + getAllDependenciesRange().copy(ret); return ret.data; } + // Left as package until the final API for this has been found + package auto getAllDependenciesRange() + const { + return + chain( + only(this.recipe.buildSettings.dependencies.byKeyValue), + this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue) + ) + .joiner() + .map!(d => PackageDependency(d.key, d.value)); + } + /** Returns a description of the package for use in IDEs or build tools. */ @@ -609,6 +634,30 @@ return ret; } + private void checkDubRequirements() + { + import dub.dependency : Dependency; + import dub.semver : isValidVersion; + import dub.version_ : dubVersion; + import std.exception : enforce; + + const dep = m_info.toolchainRequirements.dub; + + static assert(dubVersion.length); + static if (dubVersion[0] == 'v') { + enum dv = dubVersion[1 .. $]; + } + else { + enum dv = dubVersion; + } + static assert(isValidVersion(dv)); + + enforce(dep.matches(dv), + "dub-" ~ dv ~ " does not comply with toolchainRequirements.dub " + ~ "specification: " ~ m_info.toolchainRequirements.dub.toString() + ~ "\nPlease consider upgrading your DUB installation"); + } + private void fillWithDefaults() { auto bs = &m_info.buildSettings; @@ -641,7 +690,7 @@ if( !existsFile(p) ) continue; foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){ if( existsFile(p ~ fil) ) { - app_main_file = (Path(sf) ~ fil).toNativeString(); + app_main_file = (NativePath(sf) ~ fil).toNativeString(); break; } } @@ -674,18 +723,26 @@ } } - private void simpleLint() const { + package void simpleLint() + const { if (m_parentPackage) { if (m_parentPackage.path != path) { if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license) - logWarn("License in subpackage %s is different than it's parent package, this is discouraged.", name); + logWarn("Warning: License in subpackage %s is different than it's parent package, this is discouraged.", name); } } - if (name.empty) logWarn("The package in %s has no name.", path); + if (name.empty) logWarn("Warning: The package in %s has no name.", path); + bool[string] cnames; + foreach (ref c; this.recipe.configurations) { + if (c.name in cnames) + logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.", + c.name, this.name); + cnames[c.name] = true; + } } } -private string determineVersionFromSCM(Path path) +private string determineVersionFromSCM(NativePath path) { // On Windows, which is slow at running external processes, // cache the version numbers that are determined using @@ -733,7 +790,7 @@ // determines the version of a package that is stored in a GIT working copy // by invoking the "git" executable -private string determineVersionWithGIT(Path path) +private string determineVersionWithGIT(NativePath path) { import std.process; import dub.semver; diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 134eade..13134b5 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -30,30 +30,31 @@ /// packages. class PackageManager { private { - Repository[LocalPackageType] m_repositories; - Path[] m_searchPath; + Repository[] m_repositories; + NativePath[] m_searchPath; Package[] m_packages; Package[] m_temporaryPackages; bool m_disableDefaultSearchPaths = false; } - this(Path user_path, Path system_path, bool refresh_packages = true) + this(NativePath user_path, NativePath system_path, bool refresh_packages = true) { - m_repositories[LocalPackageType.user] = Repository(user_path); - m_repositories[LocalPackageType.system] = Repository(system_path); + m_repositories.length = LocalPackageType.max+1; + m_repositories[LocalPackageType.user] = Repository(user_path ~ "packages/"); + m_repositories[LocalPackageType.system] = Repository(system_path ~ "packages/"); if (refresh_packages) refresh(true); } /** Gets/sets the list of paths to search for local packages. */ - @property void searchPath(Path[] paths) + @property void searchPath(NativePath[] paths) { if (paths == m_searchPath) return; m_searchPath = paths.dup; refresh(false); } /// ditto - @property const(Path)[] searchPath() const { return m_searchPath; } + @property const(NativePath)[] searchPath() const { return m_searchPath; } /** Disables searching DUB's predefined search paths. */ @@ -66,19 +67,38 @@ /** Returns the effective list of search paths, including default ones. */ - @property const(Path)[] completeSearchPath() + @property const(NativePath)[] completeSearchPath() const { - auto ret = appender!(Path[])(); - ret.put(m_searchPath); + auto ret = appender!(NativePath[])(); + ret.put(cast(NativePath[])m_searchPath); // work around Phobos 17251 if (!m_disableDefaultSearchPaths) { - ret.put(m_repositories[LocalPackageType.user].searchPath); - ret.put(m_repositories[LocalPackageType.user].packagePath); - ret.put(m_repositories[LocalPackageType.system].searchPath); - ret.put(m_repositories[LocalPackageType.system].packagePath); + foreach (ref repo; m_repositories) { + ret.put(cast(NativePath[])repo.searchPath); + ret.put(cast(NativePath)repo.packagePath); + } } return ret.data; } + /** Sets additional (read-only) package cache paths to search for packages. + + Cache paths have the same structure as the default cache paths, such as + ".dub/packages/". + + Note that previously set custom paths will be removed when setting this + property. + */ + @property void customCachePaths(NativePath[] custom_cache_paths) + { + import std.algorithm.iteration : map; + import std.array : array; + + m_repositories.length = LocalPackageType.max+1; + m_repositories ~= custom_cache_paths.map!(p => Repository(p)).array; + + refresh(false); + } + /** Looks up a specific package. @@ -100,11 +120,11 @@ Package getPackage(string name, Version ver, bool enable_overrides = true) { if (enable_overrides) { - foreach (tp; [LocalPackageType.user, LocalPackageType.system]) - foreach (ovr; m_repositories[tp].overrides) + foreach (ref repo; m_repositories) + foreach (ovr; repo.overrides) if (ovr.package_ == name && ovr.version_.matches(ver)) { Package pack; - if (!ovr.targetPath.empty) pack = getPackage(name, ovr.targetPath); + if (!ovr.targetPath.empty) pack = getOrLoadPackage(ovr.targetPath); else pack = getPackage(name, ovr.targetVersion, false); if (pack) return pack; @@ -127,7 +147,7 @@ } /// ditto - Package getPackage(string name, Version ver, Path path) + Package getPackage(string name, Version ver, NativePath path) { auto ret = getPackage(name, path); if (!ret || ret.version_ != ver) return null; @@ -135,13 +155,13 @@ } /// ditto - Package getPackage(string name, string ver, Path path) + Package getPackage(string name, string ver, NativePath path) { return getPackage(name, Version(ver), path); } /// ditto - Package getPackage(string name, Path path) + Package getPackage(string name, NativePath path) { foreach( p; getPackageIterator(name) ) if (p.path.startsWith(path)) @@ -165,14 +185,14 @@ the package gets loaded and cached for the next call to this function. Params: - path = Path to the root directory of the package + path = NativePath to the root directory of the package recipe_path = Optional path to the recipe file of the package allow_sub_packages = Also return a sub package if it resides in the given folder Returns: The packages loaded from the given path Throws: Throws an exception if no package can be loaded */ - Package getOrLoadPackage(Path path, Path recipe_path = Path.init, bool allow_sub_packages = false) + Package getOrLoadPackage(NativePath path, NativePath recipe_path = NativePath.init, bool allow_sub_packages = false) { path.endsWithSlash = true; foreach (p; getPackageIterator()) @@ -239,15 +259,15 @@ return isManagedPath(ppath); } - /** Determines if a specifc path is within a DUB managed package folder. + /** Determines if a specific path is within a DUB managed package folder. By default, managed folders are "~/.dub/packages" and "/var/lib/dub/packages". */ - bool isManagedPath(Path path) + bool isManagedPath(NativePath path) const { foreach (rep; m_repositories) { - auto rpath = rep.packagePath; + NativePath rpath = rep.packagePath; if (path.startsWith(rpath)) return true; } @@ -266,8 +286,8 @@ if (auto ret = del(tp)) return ret; // first search local packages - foreach (tp; LocalPackageType.min .. LocalPackageType.max+1) - foreach (p; m_repositories[cast(LocalPackageType)tp].localPackages) + foreach (ref repo; m_repositories) + foreach (p; repo.localPackages) if (auto ret = del(p)) return ret; // and then all packages gathered from the search path @@ -313,7 +333,7 @@ writeLocalPackageOverridesFile(scope_); } /// ditto - void addOverride(LocalPackageType scope_, string package_, Dependency version_spec, Path target) + void addOverride(LocalPackageType scope_, string package_, Dependency version_spec, NativePath target) { m_repositories[scope_].overrides ~= PackageOverride(package_, version_spec, target); writeLocalPackageOverridesFile(scope_); @@ -336,8 +356,10 @@ /// Extracts the package supplied as a path to it's zip file to the /// destination and sets a version field in the package description. - Package storeFetchedPackage(Path zip_file_path, Json package_info, Path destination) + Package storeFetchedPackage(NativePath zip_file_path, Json package_info, NativePath destination) { + import std.range : walkLength; + auto package_name = package_info["name"].get!string; auto package_version = package_info["version"].get!string; auto clean_package_version = package_version[package_version.startsWith("~") ? 1 : 0 .. $]; @@ -361,11 +383,12 @@ logDebug("Extracting from zip."); // In a github zip, the actual contents are in a subfolder - Path zip_prefix; + alias PSegment = typeof(NativePath.init.head); + PSegment[] zip_prefix; outer: foreach(ArchiveMember am; archive.directory) { - auto path = Path(am.name); + auto path = NativePath(am.name).bySegment.array; foreach (fil; packageInfoFiles) - if (path.length == 2 && path.head.toString == fil.filename) { + if (path.length == 2 && path[$-1].toString == fil.filename) { zip_prefix = path[0 .. $-1]; break outer; } @@ -373,10 +396,21 @@ logDebug("zip root folder: %s", zip_prefix); - Path getCleanedPath(string fileName) { - auto path = Path(fileName); - if(zip_prefix != Path() && !path.startsWith(zip_prefix)) return Path(); - return path[zip_prefix.length..path.length]; + NativePath getCleanedPath(string fileName) { + auto path = NativePath(fileName); + if (zip_prefix.length && !path.bySegment.startsWith(zip_prefix)) return NativePath.init; + static if (is(typeof(path[0 .. 1]))) return path[zip_prefix.length .. $]; + else return NativePath(path.bySegment.array[zip_prefix.length .. $]); + } + + static void setAttributes(string path, ArchiveMember am) + { + import std.datetime : DosFileTimeToSysTime; + + auto mtime = DosFileTimeToSysTime(am.time); + setTimes(path, mtime, mtime); + if (auto attrs = am.fileAttributes) + std.file.setAttributes(path, attrs); } // extract & place @@ -386,7 +420,7 @@ foreach(ArchiveMember a; archive.directory) { auto cleanedPath = getCleanedPath(a.name); if(cleanedPath.empty) continue; - auto dst_path = destination~cleanedPath; + auto dst_path = destination ~ cleanedPath; logDebug("Creating %s", cleanedPath); if( dst_path.endsWithSlash ){ @@ -395,16 +429,19 @@ } else { if( !existsDirectory(dst_path.parentPath) ) mkdirRecurse(dst_path.parentPath.toNativeString()); - auto dstFile = openFile(dst_path, FileMode.createTrunc); - scope(exit) dstFile.close(); - dstFile.put(archive.expand(a)); + { + auto dstFile = openFile(dst_path, FileMode.createTrunc); + scope(exit) dstFile.close(); + dstFile.put(archive.expand(a)); + } + setAttributes(dst_path.toNativeString(), a); ++countFiles; } } logDebug("%s file(s) copied.", to!string(countFiles)); // overwrite dub.json (this one includes a version field) - auto pack = Package.load(destination, Path.init, null, package_info["version"].get!string); + auto pack = Package.load(destination, NativePath.init, null, package_info["version"].get!string); if (pack.recipePath.head != defaultPackageFilename) // Storeinfo saved a default file, this could be different to the file from the zip. @@ -415,7 +452,7 @@ } /// Removes the given the package. - void remove(in Package pack, bool force_remove) + void remove(in Package pack) { logDebug("Remove %s, version %s, path '%s'", pack.name, pack.version_, pack.path); enforce(!pack.path.empty, "Cannot remove package "~pack.name~" without a path."); @@ -445,7 +482,13 @@ logInfo("Removed package: '"~pack.name~"'"); } - Package addLocalPackage(Path path, string verName, LocalPackageType type) + /// Compatibility overload. Use the version without a `force_remove` argument instead. + void remove(in Package pack, bool force_remove) + { + remove(pack); + } + + Package addLocalPackage(NativePath path, string verName, LocalPackageType type) { path.endsWithSlash = true; auto pack = Package.load(path); @@ -471,7 +514,7 @@ return pack; } - void removeLocalPackage(Path path, LocalPackageType type) + void removeLocalPackage(NativePath path, LocalPackageType type) { path.endsWithSlash = true; @@ -495,14 +538,14 @@ } /// For the given type add another path where packages will be looked up. - void addSearchPath(Path path, LocalPackageType type) + void addSearchPath(NativePath path, LocalPackageType type) { m_repositories[type].searchPath ~= path; writeLocalPackageList(type); } /// Removes a search path from the given type. - void removeSearchPath(Path path, LocalPackageType type) + void removeSearchPath(NativePath path, LocalPackageType type) { m_repositories[type].searchPath = m_repositories[type].searchPath.filter!(p => p != path)().array(); writeLocalPackageList(type); @@ -515,9 +558,9 @@ // load locally defined packages void scanLocalPackages(LocalPackageType type) { - Path list_path = m_repositories[type].packagePath; + NativePath list_path = m_repositories[type].packagePath; Package[] packs; - Path[] paths; + NativePath[] paths; if (!m_disableDefaultSearchPaths) try { auto local_package_file = list_path ~ LocalPackagesFilename; logDiagnostic("Looking for local package map at %s", local_package_file.toNativeString()); @@ -528,7 +571,7 @@ foreach( pentry; packlist ){ try { auto name = pentry["name"].get!string; - auto path = Path(pentry["path"].get!string); + auto path = NativePath(pentry["path"].get!string); if (name == "*") { paths ~= path; } else { @@ -577,7 +620,7 @@ auto old_packages = m_packages; // rescan the system and user package folder - void scanPackageFolder(Path path) + void scanPackageFolder(NativePath path) { if( path.existsDirectory() ){ logDebug("iterating dir %s", path.toNativeString()); @@ -634,7 +677,7 @@ ovr.package_ = entry["name"].get!string; ovr.version_ = Dependency(entry["version"].get!string); if (auto pv = "targetVersion" in entry) ovr.targetVersion = Version(pv.get!string); - if (auto pv = "targetPath" in entry) ovr.targetPath = Path(pv.get!string); + if (auto pv = "targetPath" in entry) ovr.targetPath = NativePath(pv.get!string); m_repositories[type].overrides ~= ovr; } } @@ -654,18 +697,18 @@ string[] ignored_files = []; SHA1 sha1; foreach(file; dirEntries(pack.path.toNativeString(), SpanMode.depth)) { - if(file.isDir && ignored_directories.canFind(Path(file.name).head.toString())) + if(file.isDir && ignored_directories.canFind(NativePath(file.name).head.toString())) continue; - else if(ignored_files.canFind(Path(file.name).head.toString())) + else if(ignored_files.canFind(NativePath(file.name).head.toString())) continue; - sha1.put(cast(ubyte[])Path(file.name).head.toString()); + sha1.put(cast(ubyte[])NativePath(file.name).head.toString()); if(file.isDir) { - logDebug("Hashed directory name %s", Path(file.name).head); + logDebug("Hashed directory name %s", NativePath(file.name).head); } else { - sha1.put(openFile(Path(file.name)).readAll()); - logDebug("Hashed file contents from %s", Path(file.name).head); + sha1.put(openFile(NativePath(file.name)).readAll()); + logDebug("Hashed file contents from %s", NativePath(file.name).head); } } auto hash = sha1.finish(); @@ -692,7 +735,7 @@ newlist ~= entry; } - Path path = m_repositories[type].packagePath; + NativePath path = m_repositories[type].packagePath; if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString()); writeJsonFile(path ~ LocalPackagesFilename, Json(newlist)); } @@ -726,7 +769,7 @@ Package sp; if (spr.path.length) { - auto p = Path(spr.path); + auto p = NativePath(spr.path); p.normalize(); enforce(!p.absolute, "Sub package paths must be sub paths of the parent package."); auto path = pack.path ~ p; @@ -734,7 +777,7 @@ logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString()); continue; } - sp = Package.load(path, Path.init, pack); + sp = Package.load(path, NativePath.init, pack); } else sp = new Package(spr.recipe, pack.path, pack); // Add the subpackage. @@ -753,7 +796,7 @@ string package_; Dependency version_; Version targetVersion; - Path targetPath; + NativePath targetPath; this(string package_, Dependency version_, Version target_version) { @@ -762,7 +805,7 @@ this.targetVersion = target_version; } - this(string package_, Dependency version_, Path target_path) + this(string package_, Dependency version_, NativePath target_path) { this.package_ = package_; this.version_ = version_; @@ -780,15 +823,13 @@ private struct Repository { - Path path; - Path packagePath; - Path[] searchPath; + NativePath packagePath; + NativePath[] searchPath; Package[] localPackages; PackageOverride[] overrides; - this(Path path) + this(NativePath path) { - this.path = path; - this.packagePath = path ~"packages/"; + this.packagePath = path; } } diff --git a/source/dub/packagesupplier.d b/source/dub/packagesupplier.d index 540e718..244779e 100644 --- a/source/dub/packagesupplier.d +++ b/source/dub/packagesupplier.d @@ -1,257 +1,10 @@ -/** - Contains (remote) package supplier interface and implementations. +/** +deprecated("Please use dub.packagesuppliers") + Contains (remote) package supplier interface and implementations. +public import dub.packagesuppliers; Copyright: © 2012-2013 Matthias Dondorff, 2012-2016 Sönke Ludwig License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Matthias Dondorff */ +deprecated("Please use dub.packagesuppliers") module dub.packagesupplier; - -import dub.dependency; -import dub.internal.utils; -import dub.internal.vibecompat.core.log; -import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.data.json; -import dub.internal.vibecompat.inet.url; - -import std.algorithm : filter, sort; -import std.array : array; -import std.conv; -import std.datetime; -import std.exception; -import std.file; -import std.string : format; -import std.zip; - -// TODO: Could drop the "best package" behavior and let retrievePackage/ -// getPackageDescription take a Version instead of Dependency. But note -// this means that two requests to the registry are necessary to retrieve -// a package recipe instead of one (first get version list, then the -// package recipe) - -/** - Base interface for remote package suppliers. - - Provides functionality necessary to query package versions, recipes and - contents. -*/ -interface PackageSupplier { - /// Represents a single package search result. - static struct SearchResult { string name, description, version_; } - - /// Returns a human-readable representation of the package supplier. - @property string description(); - - /** Retrieves a list of all available versions(/branches) of a package. - - Throws: Throws an exception if the package name is not known, or if - an error occurred while retrieving the version list. - */ - Version[] getVersions(string package_id); - - /** Downloads a package and stores it as a ZIP file. - - Params: - path = Absolute path of the target ZIP file - package_id = Name of the package to retrieve - dep: Version constraint to match against - pre_release: If true, matches the latest pre-release version. - Otherwise prefers stable versions. - */ - void fetchPackage(Path path, string package_id, Dependency dep, bool pre_release); - - /** Retrieves only the recipe of a particular package. - - Params: - package_id = Name of the package of which to retrieve the recipe - dep: Version constraint to match against - pre_release: If true, matches the latest pre-release version. - Otherwise prefers stable versions. - */ - Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); - - /** Searches for packages matching the given search query term. - - Search queries are currently a simple list of words separated by - white space. Results will get ordered from best match to worst. - */ - SearchResult[] searchPackages(string query); -} - - -/** - File system based package supplier. - - This package supplier searches a certain directory for files with names of - the form "[package name]-[version].zip". -*/ -class FileSystemPackageSupplier : PackageSupplier { - private { - Path m_path; - } - - this(Path root) { m_path = root; } - - override @property string description() { return "file repository at "~m_path.toNativeString(); } - - Version[] getVersions(string package_id) - { - Version[] ret; - foreach (DirEntry d; dirEntries(m_path.toNativeString(), package_id~"*", SpanMode.shallow)) { - Path p = Path(d.name); - logDebug("Entry: %s", p); - enforce(to!string(p.head)[$-4..$] == ".zip"); - auto vers = p.head.toString()[package_id.length+1..$-4]; - logDebug("Version: %s", vers); - ret ~= Version(vers); - } - ret.sort(); - return ret; - } - - void fetchPackage(Path path, string packageId, Dependency dep, bool pre_release) - { - enforce(path.absolute); - logInfo("Storing package '"~packageId~"', version requirements: %s", dep); - auto filename = bestPackageFile(packageId, dep, pre_release); - enforce(existsFile(filename)); - copyFile(filename, path); - } - - Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) - { - auto filename = bestPackageFile(packageId, dep, pre_release); - return jsonFromZip(filename, "dub.json"); - } - - SearchResult[] searchPackages(string query) - { - // TODO! - return null; - } - - private Path bestPackageFile(string packageId, Dependency dep, bool pre_release) - { - Path toPath(Version ver) { - return m_path ~ (packageId ~ "-" ~ ver.toString() ~ ".zip"); - } - auto versions = getVersions(packageId).filter!(v => dep.matches(v)).array; - enforce(versions.length > 0, format("No package %s found matching %s", packageId, dep)); - foreach_reverse (ver; versions) { - if (pre_release || !ver.isPreRelease) - return toPath(ver); - } - return toPath(versions[$-1]); - } -} - - -/** - Online registry based package supplier. - - This package supplier connects to an online registry (e.g. - $(LINK https://code.dlang.org/)) to search for available packages. -*/ -class RegistryPackageSupplier : PackageSupplier { - private { - URL m_registryUrl; - struct CacheEntry { Json data; SysTime cacheTime; } - CacheEntry[string] m_metadataCache; - Duration m_maxCacheTime; - } - - this(URL registry) - { - m_registryUrl = registry; - m_maxCacheTime = 24.hours(); - } - - override @property string description() { return "registry at "~m_registryUrl.toString(); } - - Version[] getVersions(string package_id) - { - Version[] ret; - Json md = getMetadata(package_id); - foreach (json; md["versions"]) { - auto cur = Version(cast(string)json["version"]); - ret ~= cur; - } - ret.sort(); - return ret; - } - - void fetchPackage(Path path, string packageId, Dependency dep, bool pre_release) - { - import std.array : replace; - Json best = getBestPackage(packageId, dep, pre_release); - auto vers = best["version"].get!string; - auto url = m_registryUrl ~ Path(PackagesPath~"/"~packageId~"/"~vers~".zip"); - logDiagnostic("Downloading from '%s'", url); - download(url, path); - } - - Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) - { - return getBestPackage(packageId, dep, pre_release); - } - - private Json getMetadata(string packageId) - { - auto now = Clock.currTime(UTC()); - if (auto pentry = packageId in m_metadataCache) { - if (pentry.cacheTime + m_maxCacheTime > now) - return pentry.data; - m_metadataCache.remove(packageId); - } - - auto url = m_registryUrl ~ Path(PackagesPath ~ "/" ~ packageId ~ ".json"); - - logDebug("Downloading metadata for %s", packageId); - logDebug("Getting from %s", url); - - auto jsonData = cast(string)download(url); - Json json = parseJsonString(jsonData, url.toString()); - // strip readme data (to save size and time) - foreach (ref v; json["versions"]) - v.remove("readme"); - m_metadataCache[packageId] = CacheEntry(json, now); - return json; - } - - SearchResult[] searchPackages(string query) { - import std.uri : encodeComponent; - auto url = m_registryUrl; - url.localURI = "/api/packages/search?q="~encodeComponent(query); - string data; - try - data = cast(string)download(url); - catch (Exception) - return null; - import std.algorithm : map; - return data.parseJson.opt!(Json[]) - .map!(j => SearchResult(j["name"].opt!string, j["description"].opt!string, j["version"].opt!string)) - .array; - } - - private Json getBestPackage(string packageId, Dependency dep, bool pre_release) - { - Json md = getMetadata(packageId); - Json best = null; - Version bestver; - foreach (json; md["versions"]) { - auto cur = Version(cast(string)json["version"]); - if (!dep.matches(cur)) continue; - if (best == null) best = json; - else if (pre_release) { - if (cur > bestver) best = json; - } else if (bestver.isPreRelease) { - if (!cur.isPreRelease || cur > bestver) best = json; - } else if (!cur.isPreRelease && cur > bestver) best = json; - bestver = Version(cast(string)best["version"]); - } - enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString()); - return best; - } -} - -private enum PackagesPath = "packages"; +public import dub.packagesuppliers; diff --git a/source/dub/packagesuppliers/fallback.d b/source/dub/packagesuppliers/fallback.d new file mode 100644 index 0000000..39a61e6 --- /dev/null +++ b/source/dub/packagesuppliers/fallback.d @@ -0,0 +1,80 @@ +module dub.packagesuppliers.fallback; + +import dub.packagesuppliers.packagesupplier; +import std.typecons : AutoImplement; + +package abstract class AbstractFallbackPackageSupplier : PackageSupplier +{ + protected import core.time : minutes; + protected import std.datetime : Clock, SysTime; + + static struct Pair { PackageSupplier ps; SysTime failTime; } + protected Pair[] m_suppliers; + + this(PackageSupplier[] suppliers) + { + assert(suppliers.length); + m_suppliers.length = suppliers.length; + foreach (i, ps; suppliers) + m_suppliers[i].ps = ps; + } + + override @property string description() + { + import std.algorithm.iteration : map; + import std.format : format; + return format("%s (fallbacks %-(%s, %))", m_suppliers[0].ps.description, + m_suppliers[1 .. $].map!(pair => pair.ps.description)); + } + + // Workaround https://issues.dlang.org/show_bug.cgi?id=2525 + abstract override Version[] getVersions(string package_id); + abstract override void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); + abstract override Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); + abstract override SearchResult[] searchPackages(string query); +} + + +/** + Combines two package suppliers and uses the second as fallback to handle failures. + + Assumes that both registries serve the same packages (--mirror). +*/ +package(dub) alias FallbackPackageSupplier = AutoImplement!(AbstractFallbackPackageSupplier, fallback); + +private template fallback(T, alias func) +{ + import std.format : format; + enum fallback = q{ + import dub.internal.vibecompat.core.log : logDiagnostic; + + Exception firstEx; + try + return m_suppliers[0].ps.%1$s(args); + catch (Exception e) + { + logDiagnostic("Package supplier %%s failed with '%%s', trying fallbacks.", + m_suppliers[0].ps.description, e.msg); + firstEx = e; + } + + immutable now = Clock.currTime; + foreach (ref pair; m_suppliers[1 .. $]) + { + if (pair.failTime > now - 10.minutes) + continue; + try + { + scope (success) logDiagnostic("Fallback %%s succeeded", pair.ps.description); + return pair.ps.%1$s(args); + } + catch (Exception e) + { + pair.failTime = now; + logDiagnostic("Fallback package supplier %%s failed with '%%s'.", + pair.ps.description, e.msg); + } + } + throw firstEx; + }.format(__traits(identifier, func)); +} diff --git a/source/dub/packagesuppliers/filesystem.d b/source/dub/packagesuppliers/filesystem.d new file mode 100644 index 0000000..61b125c --- /dev/null +++ b/source/dub/packagesuppliers/filesystem.d @@ -0,0 +1,90 @@ +module dub.packagesuppliers.filesystem; + +import dub.packagesuppliers.packagesupplier; + +/** + File system based package supplier. + + This package supplier searches a certain directory for files with names of + the form "[package name]-[version].zip". +*/ +class FileSystemPackageSupplier : PackageSupplier { + import dub.internal.vibecompat.core.log; + version (Have_vibe_core) import dub.internal.vibecompat.inet.path : toNativeString; + import std.exception : enforce; + private { + NativePath m_path; + } + + this(NativePath root) { m_path = root; } + + override @property string description() { return "file repository at "~m_path.toNativeString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + import std.file : dirEntries, DirEntry, SpanMode; + import std.conv : to; + Version[] ret; + foreach (DirEntry d; dirEntries(m_path.toNativeString(), package_id~"*", SpanMode.shallow)) { + NativePath p = NativePath(d.name); + logDebug("Entry: %s", p); + enforce(to!string(p.head)[$-4..$] == ".zip"); + auto vers = p.head.toString()[package_id.length+1..$-4]; + logDebug("Version: %s", vers); + ret ~= Version(vers); + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import dub.internal.vibecompat.core.file : copyFile, existsFile; + enforce(path.absolute); + logInfo("Storing package '"~packageId~"', version requirements: %s", dep); + auto filename = bestPackageFile(packageId, dep, pre_release); + enforce(existsFile(filename)); + copyFile(filename, path); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + import std.array : split; + import std.path : stripExtension; + import dub.internal.utils : packageInfoFileFromZip; + import dub.recipe.io : parsePackageRecipe; + import dub.recipe.json : toJson; + + auto filePath = bestPackageFile(packageId, dep, pre_release); + string packageFileName; + string packageFileContent = packageInfoFileFromZip(filePath, packageFileName); + auto recipe = parsePackageRecipe(packageFileContent, packageFileName); + Json json = toJson(recipe); + json["version"] = filePath.toNativeString().split("-")[$-1].stripExtension(); + return json; + } + + SearchResult[] searchPackages(string query) + { + // TODO! + return null; + } + + private NativePath bestPackageFile(string packageId, Dependency dep, bool pre_release) + { + import std.algorithm.iteration : filter; + import std.array : array; + import std.format : format; + NativePath toPath(Version ver) { + return m_path ~ (packageId ~ "-" ~ ver.toString() ~ ".zip"); + } + auto versions = getVersions(packageId).filter!(v => dep.matches(v)).array; + enforce(versions.length > 0, format("No package %s found matching %s", packageId, dep)); + foreach_reverse (ver; versions) { + if (pre_release || !ver.isPreRelease) + return toPath(ver); + } + return toPath(versions[$-1]); + } +} diff --git a/source/dub/packagesuppliers/maven.d b/source/dub/packagesuppliers/maven.d new file mode 100644 index 0000000..eaee689 --- /dev/null +++ b/source/dub/packagesuppliers/maven.d @@ -0,0 +1,125 @@ +module dub.packagesuppliers.maven; + +import dub.packagesuppliers.packagesupplier; + +/** + Maven repository based package supplier. + + This package supplier connects to a maven repository + to search for available packages. +*/ +class MavenRegistryPackageSupplier : PackageSupplier { + import dub.internal.utils : retryDownload, HTTPStatusException; + import dub.internal.vibecompat.data.json : serializeToJson; + import dub.internal.vibecompat.core.log; + import dub.internal.vibecompat.inet.url : URL; + + import std.datetime : Clock, Duration, hours, SysTime, UTC; + + private { + enum httpTimeout = 16; + URL m_mavenUrl; + struct CacheEntry { Json data; SysTime cacheTime; } + CacheEntry[string] m_metadataCache; + Duration m_maxCacheTime; + } + + this(URL mavenUrl) + { + m_mavenUrl = mavenUrl; + m_maxCacheTime = 24.hours(); + } + + override @property string description() { return "maven repository at "~m_mavenUrl.toString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + auto md = getMetadata(package_id); + if (md.type == Json.Type.null_) + return null; + Version[] ret; + foreach (json; md["versions"]) { + auto cur = Version(json["version"].get!string); + ret ~= cur; + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import std.format : format; + auto md = getMetadata(packageId); + Json best = getBestPackage(md, packageId, dep, pre_release); + if (best.type == Json.Type.null_) + return; + auto vers = best["version"].get!string; + auto url = m_mavenUrl~NativePath("%s/%s/%s-%s.zip".format(packageId, vers, packageId, vers)); + + try { + retryDownload(url, path, 3, httpTimeout); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else logDebug("Failed to download package %s from %s", packageId, url); + } + catch(Exception e) { + logDebug("Failed to download package %s from %s", packageId, url); + } + throw new Exception("Failed to download package %s from %s".format(packageId, url)); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + auto md = getMetadata(packageId); + return getBestPackage(md, packageId, dep, pre_release); + } + + private Json getMetadata(string packageId) + { + import std.xml; + + auto now = Clock.currTime(UTC()); + if (auto pentry = packageId in m_metadataCache) { + if (pentry.cacheTime + m_maxCacheTime > now) + return pentry.data; + m_metadataCache.remove(packageId); + } + + auto url = m_mavenUrl~NativePath(packageId~"/maven-metadata.xml"); + + logDebug("Downloading maven metadata for %s", packageId); + string xmlData; + + try + xmlData = cast(string)retryDownload(url, 3, httpTimeout); + catch(HTTPStatusException e) { + if (e.status == 404) { + logDebug("Maven metadata %s not found at %s (404): %s", packageId, description, e.msg); + return Json(null); + } + else throw e; + } + + auto json = Json(["name": Json(packageId), "versions": Json.emptyArray]); + auto xml = new DocumentParser(xmlData); + + xml.onStartTag["versions"] = (ElementParser xml) { + xml.onEndTag["version"] = (in Element e) { + json["versions"] ~= serializeToJson(["name": packageId, "version": e.text]); + }; + xml.parse(); + }; + xml.parse(); + m_metadataCache[packageId] = CacheEntry(json, now); + return json; + } + + SearchResult[] searchPackages(string query) + { + return []; + } +} + diff --git a/source/dub/packagesuppliers/package.d b/source/dub/packagesuppliers/package.d new file mode 100644 index 0000000..5bb8057 --- /dev/null +++ b/source/dub/packagesuppliers/package.d @@ -0,0 +1,10 @@ +module dub.packagesuppliers; + +/** + Contains (remote) package supplier interface and implementations. +*/ +public import dub.packagesuppliers.fallback; +public import dub.packagesuppliers.filesystem; +public import dub.packagesuppliers.packagesupplier; +public import dub.packagesuppliers.maven; +public import dub.packagesuppliers.registry; diff --git a/source/dub/packagesuppliers/packagesupplier.d b/source/dub/packagesuppliers/packagesupplier.d new file mode 100644 index 0000000..4018977 --- /dev/null +++ b/source/dub/packagesuppliers/packagesupplier.d @@ -0,0 +1,82 @@ +module dub.packagesuppliers.packagesupplier; + +public import dub.dependency : Dependency, Version; +public import dub.internal.vibecompat.core.file : NativePath; +public import dub.internal.vibecompat.data.json : Json; + +/** + Base interface for remote package suppliers. + + Provides functionality necessary to query package versions, recipes and + contents. +*/ +interface PackageSupplier { + /// Represents a single package search result. + static struct SearchResult { string name, description, version_; } + + /// Returns a human-readable representation of the package supplier. + @property string description(); + + /** Retrieves a list of all available versions(/branches) of a package. + + Throws: Throws an exception if the package name is not known, or if + an error occurred while retrieving the version list. + */ + Version[] getVersions(string package_id); + + /** Downloads a package and stores it as a ZIP file. + + Params: + path = Absolute path of the target ZIP file + package_id = Name of the package to retrieve + dep: Version constraint to match against + pre_release: If true, matches the latest pre-release version. + Otherwise prefers stable versions. + */ + void fetchPackage(NativePath path, string package_id, Dependency dep, bool pre_release); + + /** Retrieves only the recipe of a particular package. + + Params: + package_id = Name of the package of which to retrieve the recipe + dep: Version constraint to match against + pre_release: If true, matches the latest pre-release version. + Otherwise prefers stable versions. + */ + Json fetchPackageRecipe(string package_id, Dependency dep, bool pre_release); + + /** Searches for packages matching the given search query term. + + Search queries are currently a simple list of words separated by + white space. Results will get ordered from best match to worst. + */ + SearchResult[] searchPackages(string query); +} + +// TODO: Could drop the "best package" behavior and let retrievePackage/ +// getPackageDescription take a Version instead of Dependency. But note +// this means that two requests to the registry are necessary to retrieve +// a package recipe instead of one (first get version list, then the +// package recipe) + +package Json getBestPackage(Json metadata, string packageId, Dependency dep, bool pre_release) +{ + import std.exception : enforce; + if (metadata.type == Json.Type.null_) + return metadata; + Json best = null; + Version bestver; + foreach (json; metadata["versions"]) { + auto cur = Version(json["version"].get!string); + if (!dep.matches(cur)) continue; + if (best == null) best = json; + else if (pre_release) { + if (cur > bestver) best = json; + } else if (bestver.isPreRelease) { + if (!cur.isPreRelease || cur > bestver) best = json; + } else if (!cur.isPreRelease && cur > bestver) best = json; + bestver = Version(cast(string)best["version"]); + } + enforce(best != null, "No package candidate found for "~packageId~" "~dep.toString()); + return best; +} diff --git a/source/dub/packagesuppliers/registry.d b/source/dub/packagesuppliers/registry.d new file mode 100644 index 0000000..470f636 --- /dev/null +++ b/source/dub/packagesuppliers/registry.d @@ -0,0 +1,119 @@ +module dub.packagesuppliers.registry; + +import dub.packagesuppliers.packagesupplier; + +package enum PackagesPath = "packages"; + +/** + Online registry based package supplier. + + This package supplier connects to an online registry (e.g. + $(LINK https://code.dlang.org/)) to search for available packages. +*/ +class RegistryPackageSupplier : PackageSupplier { + import dub.internal.utils : download, retryDownload, HTTPStatusException; + import dub.internal.vibecompat.core.log; + import dub.internal.vibecompat.data.json : parseJson, parseJsonString, serializeToJson; + import dub.internal.vibecompat.inet.url : URL; + + import std.datetime : Clock, Duration, hours, SysTime, UTC; + private { + URL m_registryUrl; + struct CacheEntry { Json data; SysTime cacheTime; } + CacheEntry[string] m_metadataCache; + Duration m_maxCacheTime; + } + + this(URL registry) + { + m_registryUrl = registry; + m_maxCacheTime = 24.hours(); + } + + override @property string description() { return "registry at "~m_registryUrl.toString(); } + + Version[] getVersions(string package_id) + { + import std.algorithm.sorting : sort; + auto md = getMetadata(package_id); + if (md.type == Json.Type.null_) + return null; + Version[] ret; + foreach (json; md["versions"]) { + auto cur = Version(cast(string)json["version"]); + ret ~= cur; + } + ret.sort(); + return ret; + } + + void fetchPackage(NativePath path, string packageId, Dependency dep, bool pre_release) + { + import std.array : replace; + import std.format : format; + auto md = getMetadata(packageId); + Json best = getBestPackage(md, packageId, dep, pre_release); + if (best.type == Json.Type.null_) + return; + auto vers = best["version"].get!string; + auto url = m_registryUrl ~ NativePath(PackagesPath~"/"~packageId~"/"~vers~".zip"); + try { + retryDownload(url, path); + return; + } + catch(HTTPStatusException e) { + if (e.status == 404) throw e; + else logDebug("Failed to download package %s from %s", packageId, url); + } + catch(Exception e) { + logDebug("Failed to download package %s from %s", packageId, url); + } + throw new Exception("Failed to download package %s from %s".format(packageId, url)); + } + + Json fetchPackageRecipe(string packageId, Dependency dep, bool pre_release) + { + auto md = getMetadata(packageId); + return getBestPackage(md, packageId, dep, pre_release); + } + + private Json getMetadata(string packageId) + { + auto now = Clock.currTime(UTC()); + if (auto pentry = packageId in m_metadataCache) { + if (pentry.cacheTime + m_maxCacheTime > now) + return pentry.data; + m_metadataCache.remove(packageId); + } + + auto url = m_registryUrl ~ NativePath("api/packages/infos?packages=[\"" ~ + packageId ~ "\"]&include_dependencies=true&minimize=true"); + + logDebug("Downloading metadata for %s", packageId); + string jsonData; + + jsonData = cast(string)retryDownload(url); + + Json json = parseJsonString(jsonData, url.toString()); + foreach (pkg, info; json.get!(Json[string])) + { + logDebug("adding %s to metadata cache", pkg); + m_metadataCache[pkg] = CacheEntry(info, now); + } + return json[packageId]; + } + + SearchResult[] searchPackages(string query) { + import std.array : array; + import std.algorithm.iteration : map; + import std.uri : encodeComponent; + auto url = m_registryUrl; + url.localURI = "/api/packages/search?q="~encodeComponent(query); + string data; + data = cast(string)retryDownload(url); + return data.parseJson.opt!(Json[]) + .map!(j => SearchResult(j["name"].opt!string, j["description"].opt!string, j["version"].opt!string)) + .array; + } +} + diff --git a/source/dub/platform.d b/source/dub/platform.d index 8350a21..88da7b2 100644 --- a/source/dub/platform.d +++ b/source/dub/platform.d @@ -1,5 +1,5 @@ /** - Build platform identification and speficiation matching. + Build platform identification and specification matching. This module is useful for determining the build platform for a certain machine and compiler invocation. Example applications include classifying @@ -8,7 +8,7 @@ It also contains means to match build platforms against a platform specification string as used in package reciptes. - Copyright: © 2012-2016 rejectedsoftware e.K. + Copyright: © 2012-2017 rejectedsoftware e.K. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Sönke Ludwig */ @@ -16,6 +16,86 @@ import std.array; +// archCheck, compilerCheck, and platformCheck are used below and in +// generatePlatformProbeFile, so they've been extracted into these strings +// that can be reused. +// Try to not use phobos in the probes to avoid long import times. +/// private +enum string platformCheck = q{ + string[] ret; + version(Windows) ret ~= "windows"; + version(linux) ret ~= "linux"; + version(Posix) ret ~= "posix"; + version(OSX) ret ~= "osx"; + version(FreeBSD) ret ~= "freebsd"; + version(OpenBSD) ret ~= "openbsd"; + version(NetBSD) ret ~= "netbsd"; + version(DragonFlyBSD) ret ~= "dragonflybsd"; + version(BSD) ret ~= "bsd"; + version(Solaris) ret ~= "solaris"; + version(AIX) ret ~= "aix"; + version(Haiku) ret ~= "haiku"; + version(SkyOS) ret ~= "skyos"; + version(SysV3) ret ~= "sysv3"; + version(SysV4) ret ~= "sysv4"; + version(Hurd) ret ~= "hurd"; + version(Android) ret ~= "android"; + version(Cygwin) ret ~= "cygwin"; + version(MinGW) ret ~= "mingw"; + return ret; +}; + +/// private +enum string archCheck = q{ + string[] ret; + version(X86) ret ~= "x86"; + version(X86_64) ret ~= "x86_64"; + version(ARM) ret ~= "arm"; + version(AArch64) ret ~= "aarch64"; + version(ARM_Thumb) ret ~= "arm_thumb"; + version(ARM_SoftFloat) ret ~= "arm_softfloat"; + version(ARM_HardFloat) ret ~= "arm_hardfloat"; + version(PPC) ret ~= "ppc"; + version(PPC_SoftFP) ret ~= "ppc_softfp"; + version(PPC_HardFP) ret ~= "ppc_hardfp"; + version(PPC64) ret ~= "ppc64"; + version(IA64) ret ~= "ia64"; + version(MIPS) ret ~= "mips"; + version(MIPS32) ret ~= "mips32"; + version(MIPS64) ret ~= "mips64"; + version(MIPS_O32) ret ~= "mips_o32"; + version(MIPS_N32) ret ~= "mips_n32"; + version(MIPS_O64) ret ~= "mips_o64"; + version(MIPS_N64) ret ~= "mips_n64"; + version(MIPS_EABI) ret ~= "mips_eabi"; + version(MIPS_NoFloat) ret ~= "mips_nofloat"; + version(MIPS_SoftFloat) ret ~= "mips_softfloat"; + version(MIPS_HardFloat) ret ~= "mips_hardfloat"; + version(SPARC) ret ~= "sparc"; + version(SPARC_V8Plus) ret ~= "sparc_v8plus"; + version(SPARC_SoftFP) ret ~= "sparc_softfp"; + version(SPARC_HardFP) ret ~= "sparc_hardfp"; + version(SPARC64) ret ~= "sparc64"; + version(S390) ret ~= "s390"; + version(S390X) ret ~= "s390x"; + version(HPPA) ret ~= "hppa"; + version(HPPA64) ret ~= "hppa64"; + version(SH) ret ~= "sh"; + version(SH64) ret ~= "sh64"; + version(Alpha) ret ~= "alpha"; + version(Alpha_SoftFP) ret ~= "alpha_softfp"; + version(Alpha_HardFP) ret ~= "alpha_hardfp"; + return ret; +}; + +/// private +enum string compilerCheck = q{ + version(DigitalMars) return "dmd"; + else version(GNU) return "gdc"; + else version(LDC) return "ldc"; + else version(SDC) return "sdc"; + else return null; +}; /** Determines the full build platform used for the current build. @@ -45,27 +125,7 @@ */ string[] determinePlatform() { - auto ret = appender!(string[])(); - version(Windows) ret.put("windows"); - version(linux) ret.put("linux"); - version(Posix) ret.put("posix"); - version(OSX) ret.put("osx"); - version(FreeBSD) ret.put("freebsd"); - version(OpenBSD) ret.put("openbsd"); - version(NetBSD) ret.put("netbsd"); - version(DragonFlyBSD) ret.put("dragonflybsd"); - version(BSD) ret.put("bsd"); - version(Solaris) ret.put("solaris"); - version(AIX) ret.put("aix"); - version(Haiku) ret.put("haiku"); - version(SkyOS) ret.put("skyos"); - version(SysV3) ret.put("sysv3"); - version(SysV4) ret.put("sysv4"); - version(Hurd) ret.put("hurd"); - version(Android) ret.put("android"); - version(Cygwin) ret.put("cygwin"); - version(MinGW) ret.put("mingw"); - return ret.data; + mixin(platformCheck); } /** Returns a list of architecture identifiers that apply to the current @@ -79,61 +139,19 @@ */ string[] determineArchitecture() { - auto ret = appender!(string[])(); - version(X86) ret.put("x86"); - version(X86_64) ret.put("x86_64"); - version(ARM) ret.put("arm"); - version(ARM_Thumb) ret.put("arm_thumb"); - version(ARM_SoftFloat) ret.put("arm_softfloat"); - version(ARM_HardFloat) ret.put("arm_hardfloat"); - version(ARM64) ret.put("arm64"); - version(PPC) ret.put("ppc"); - version(PPC_SoftFP) ret.put("ppc_softfp"); - version(PPC_HardFP) ret.put("ppc_hardfp"); - version(PPC64) ret.put("ppc64"); - version(IA64) ret.put("ia64"); - version(MIPS) ret.put("mips"); - version(MIPS32) ret.put("mips32"); - version(MIPS64) ret.put("mips64"); - version(MIPS_O32) ret.put("mips_o32"); - version(MIPS_N32) ret.put("mips_n32"); - version(MIPS_O64) ret.put("mips_o64"); - version(MIPS_N64) ret.put("mips_n64"); - version(MIPS_EABI) ret.put("mips_eabi"); - version(MIPS_NoFloat) ret.put("mips_nofloat"); - version(MIPS_SoftFloat) ret.put("mips_softfloat"); - version(MIPS_HardFloat) ret.put("mips_hardfloat"); - version(SPARC) ret.put("sparc"); - version(SPARC_V8Plus) ret.put("sparc_v8plus"); - version(SPARC_SoftFP) ret.put("sparc_softfp"); - version(SPARC_HardFP) ret.put("sparc_hardfp"); - version(SPARC64) ret.put("sparc64"); - version(S390) ret.put("s390"); - version(S390X) ret.put("s390x"); - version(HPPA) ret.put("hppa"); - version(HPPA64) ret.put("hppa64"); - version(SH) ret.put("sh"); - version(SH64) ret.put("sh64"); - version(Alpha) ret.put("alpha"); - version(Alpha_SoftFP) ret.put("alpha_softfp"); - version(Alpha_HardFP) ret.put("alpha_hardfp"); - return ret.data; + mixin(archCheck); } /** Determines the canonical compiler name used for the current build. - The possible values currently are "dmd", "gdc", "ldc2" or "sdc". If an + The possible values currently are "dmd", "gdc", "ldc" or "sdc". If an unknown compiler is used, this function will return an empty string. See_Also: `determineBuildPlatform` */ string determineCompiler() { - version(DigitalMars) return "dmd"; - else version(GNU) return "gdc"; - else version(LDC) return "ldc2"; - else version(SDC) return "sdc"; - else return null; + mixin(compilerCheck); } /** Matches a platform specification string against a build platform. @@ -144,7 +162,7 @@ So the following strings are valid specifications: `"-windows-x86-dmd"`, `"-dmd"`, `"-arm"`, `"-arm-dmd"`, `"-windows-dmd"` - + Params: platform = The build platform to match agains the platform specification specification = The specification being matched. It must either be an @@ -181,7 +199,7 @@ } if (platform.compiler == splitted.front) { splitted.popFront(); - enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); + enforce(splitted.empty, "No valid specification! The compiler has to be the last element: " ~ specification); return true; } return false; @@ -216,4 +234,25 @@ string compilerBinary; /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) int frontendVersion; + /// Compiler version e.g. "1.11.0" + string compilerVersion; + /// Frontend version string from frontendVersion + /// e.g: 2067 => "2.067" + string frontendVersionString() const + { + import std.format : format; + + const maj = frontendVersion / 1000; + const min = frontendVersion % 1000; + return format("%d.%03d", maj, min); + } + /// + unittest + { + BuildPlatform bp; + bp.frontendVersion = 2067; + assert(bp.frontendVersionString == "2.067"); + } } + + diff --git a/source/dub/project.d b/source/dub/project.d index ffe4295..fc81408 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -17,21 +17,14 @@ import dub.internal.vibecompat.inet.url; import dub.package_; import dub.packagemanager; -import dub.packagesupplier; import dub.generators.generator; - -// todo: cleanup imports. import std.algorithm; import std.array; -import std.conv; +import std.conv : to; import std.datetime; -import std.exception; -import std.file; -import std.process; +import std.exception : enforce; import std.string; -import std.typecons; -import std.zip; import std.encoding : sanitize; /** @@ -50,7 +43,8 @@ Package[] m_dependencies; Package[][Package] m_dependees; SelectedVersions m_selections; - bool m_hasAllDependencies; + string[] m_missingDependencies; + string[string] m_overriddenConfigs; } /** Loads a project. @@ -61,7 +55,7 @@ project_path = Path of the root package to load pack = An existing `Package` instance to use as the root package */ - this(PackageManager package_manager, Path project_path) + this(PackageManager package_manager, NativePath project_path) { Package pack; auto packageFile = Package.findPackageFile(project_path); @@ -120,7 +114,10 @@ to `selections`, or to use `Dub.upgrade` to automatically select all missing dependencies. */ - bool hasAllDependencies() const { return m_hasAllDependencies; } + bool hasAllDependencies() const { return m_missingDependencies.length == 0; } + + /// Sorted list of missing dependencies. + string[] missingDependencies() { return m_missingDependencies; } /** Allows iteration of the dependency tree in topological order */ @@ -208,6 +205,27 @@ return cfgs[m_rootPackage.name]; } + /** Overrides the configuration chosen for a particular package in the + dependency graph. + + Setting a certain configuration here is equivalent to removing all + but one configuration from the package. + + Params: + package_ = The package for which to force selecting a certain + dependency + config = Name of the configuration to force + */ + void overrideConfiguration(string package_, string config) + { + auto p = getDependency(package_, true); + enforce(p !is null, + format("Package '%s', marked for configuration override, is not present in dependency graph.", package_)); + enforce(p.configurations.canFind(config), + format("Package '%s' does not have a configuration named '%s'.", package_, config)); + m_overriddenConfigs[package_] = config; + } + /** Performs basic validation of various aspects of the package. This will emit warnings to `stderr` if any discouraged names or @@ -246,8 +264,40 @@ d.name, SelectedVersions.defaultFile); } + // search for orphan sub configurations + void warnSubConfig(string pack, string config) { + logWarn("The sub configuration directive \"%s\" -> \"%s\" " + ~ "references a package that is not specified as a dependency " + ~ "and will have no effect.", pack, config); + } + void checkSubConfig(string pack, string config) { + auto p = getDependency(pack, true); + if (p && !p.configurations.canFind(config)) { + logWarn("The sub configuration directive \"%s\" -> \"%s\" " + ~ "references a configuration that does not exist.", + pack, config); + } + } + auto globalbs = m_rootPackage.getBuildSettings(); + foreach (p, c; globalbs.subConfigurations) { + if (p !in globalbs.dependencies) warnSubConfig(p, c); + else checkSubConfig(p, c); + } + foreach (c; m_rootPackage.configurations) { + auto bs = m_rootPackage.getBuildSettings(c); + foreach (p, c; bs.subConfigurations) { + if (p !in bs.dependencies && p !in globalbs.dependencies) + warnSubConfig(p, c); + else checkSubConfig(p, c); + } + } + + // check for version specification mismatches bool[Package] visited; void validateDependenciesRec(Package pack) { + // perform basic package linting + pack.simpleLint(); + foreach (d; pack.getAllDependencies()) { auto basename = getBasePackageName(d.name); if (m_selections.hasSelectedVersion(basename)) { @@ -258,7 +308,7 @@ } } - auto deppack = getDependency(name, true); + auto deppack = getDependency(d.name, true); if (deppack in visited) continue; visited[deppack] = true; if (deppack) validateDependenciesRec(deppack); @@ -271,7 +321,7 @@ void reinit() { m_dependencies = null; - m_hasAllDependencies = true; + m_missingDependencies = []; m_packageManager.refresh(false); void collectDependenciesRec(Package pack, int depth = 0) @@ -299,7 +349,7 @@ try p = m_packageManager.getSubPackage(m_rootPackage.basePackage, subname, false); catch (Exception e) { logDiagnostic("%sError getting sub package %s: %s", indent, dep.name, e.msg); - if (is_desired) m_hasAllDependencies = false; + if (is_desired) m_missingDependencies ~= dep.name; continue; } } else if (m_selections.hasSelectedVersion(basename)) { @@ -308,7 +358,7 @@ else { auto path = vspec.path; if (!path.absolute) path = m_rootPackage.path ~ path; - p = m_packageManager.getOrLoadPackage(path, Path.init, true); + p = m_packageManager.getOrLoadPackage(path, NativePath.init, true); if (subname.length) p = m_packageManager.getSubPackage(p, subname, true); } } else if (m_dependencies.canFind!(d => getBasePackageName(d.name) == basename)) { @@ -323,10 +373,10 @@ } if (!p && !vspec.path.empty) { - Path path = vspec.path; + NativePath path = vspec.path; if (!path.absolute) path = pack.path ~ path; - logDiagnostic("%sAdding local %s", indent, path); - p = m_packageManager.getOrLoadPackage(path, Path.init, true); + logDiagnostic("%sAdding local %s in %s", indent, dep.name, path); + p = m_packageManager.getOrLoadPackage(path, NativePath.init, true); if (p.parentPackage !is null) { logWarn("%sSub package %s must be referenced using the path to it's parent package.", indent, dep.name); p = p.parentPackage; @@ -339,14 +389,15 @@ if (!p) { logDiagnostic("%sMissing dependency %s %s of %s", indent, dep.name, vspec, pack.name); - if (is_desired) m_hasAllDependencies = false; + if (is_desired) m_missingDependencies ~= dep.name; continue; } if (!m_dependencies.canFind(p)) { logDiagnostic("%sFound dependency %s %s", indent, dep.name, vspec.toString()); m_dependencies ~= p; - p.warnOnSpecialCompilerFlags(); + if (basename == m_rootPackage.basePackage.name) + p.warnOnSpecialCompilerFlags(); collectDependenciesRec(p, depth+1); } @@ -355,6 +406,7 @@ } } collectDependenciesRec(m_rootPackage); + m_missingDependencies.sort(); } /// Returns the name of the root package. @@ -377,11 +429,11 @@ foreach (d; p.getAllDependencies()) parents[d.name] ~= p.name; - size_t createConfig(string pack, string config) { foreach (i, v; configs) if (v.pack == pack && v.config == config) return i; + assert(pack !in m_overriddenConfigs || config == m_overriddenConfigs[pack]); logDebug("Add config %s %s", pack, config); configs ~= Vertex(pack, config); return configs.length-1; @@ -401,12 +453,26 @@ void removeConfig(size_t i) { logDebug("Eliminating config %s for %s", configs[i].config, configs[i].pack); - configs = configs.remove(i); - edges = edges.filter!(e => e.from != i && e.to != i).array(); - foreach (ref e; edges) { - if (e.from > i) e.from--; - if (e.to > i) e.to--; - } + auto had_dep_to_pack = new bool[configs.length]; + auto still_has_dep_to_pack = new bool[configs.length]; + + edges = edges.filter!((e) { + if (e.to == i) { + had_dep_to_pack[e.from] = true; + return false; + } else if (configs[e.to].pack == configs[i].pack) { + still_has_dep_to_pack[e.from] = true; + } + if (e.from == i) return false; + return true; + }).array; + + configs[i] = Vertex.init; // mark config as removed + + // also remove any configs that cannot be satisfied anymore + foreach (j; 0 .. configs.length) + if (j != i && had_dep_to_pack[j] && !still_has_dep_to_pack[j]) + removeConfig(j); } bool isReachable(string pack, string conf) { @@ -430,6 +496,39 @@ } string[] allconfigs_path; + + void determineDependencyConfigs(in Package p, string c) + { + string[][string] depconfigs; + foreach (d; p.getAllDependencies()) { + auto dp = getDependency(d.name, true); + if (!dp) continue; + + string[] cfgs; + if (auto pc = dp.name in m_overriddenConfigs) cfgs = [*pc]; + else { + auto subconf = p.getSubConfiguration(c, dp, platform); + if (!subconf.empty) cfgs = [subconf]; + else cfgs = dp.getPlatformConfigurations(platform); + } + cfgs = cfgs.filter!(c => haveConfig(d.name, c)).array; + + // if no valid configuration was found for a dependency, don't include the + // current configuration + if (!cfgs.length) { + logDebug("Skip %s %s (missing configuration for %s)", p.name, c, dp.name); + return; + } + depconfigs[d.name] = cfgs; + } + + // add this configuration to the graph + size_t cidx = createConfig(p.name, c); + foreach (d; p.getAllDependencies()) + foreach (sc; depconfigs.get(d.name, null)) + createEdge(cidx, createConfig(d.name, sc)); + } + // create a graph of all possible package configurations (package, config) -> (subpackage, subconfig) void determineAllConfigs(in Package p) { @@ -446,33 +545,11 @@ } // for each configuration, determine the configurations usable for the dependencies - outer: foreach (c; p.getPlatformConfigurations(platform, p is m_rootPackage && allow_non_library)) { - string[][string] depconfigs; - foreach (d; p.getAllDependencies()) { - auto dp = getDependency(d.name, true); - if (!dp) continue; - - string[] cfgs; - auto subconf = p.getSubConfiguration(c, dp, platform); - if (!subconf.empty) cfgs = [subconf]; - else cfgs = dp.getPlatformConfigurations(platform); - cfgs = cfgs.filter!(c => haveConfig(d.name, c)).array; - - // if no valid configuration was found for a dependency, don't include the - // current configuration - if (!cfgs.length) { - logDebug("Skip %s %s (missing configuration for %s)", p.name, c, dp.name); - continue outer; - } - depconfigs[d.name] = cfgs; - } - - // add this configuration to the graph - size_t cidx = createConfig(p.name, c); - foreach (d; p.getAllDependencies()) - foreach (sc; depconfigs.get(d.name, null)) - createEdge(cidx, createConfig(d.name, sc)); - } + if (auto pc = p.name in m_overriddenConfigs) + determineDependencyConfigs(p, *pc); + else + foreach (c; p.getPlatformConfigurations(platform, p is m_rootPackage && allow_non_library)) + determineDependencyConfigs(p, c); } if (config.length) createConfig(m_rootPackage.name, config); determineAllConfigs(m_rootPackage); @@ -482,26 +559,24 @@ do { // remove all configs that are not reachable by all parent packages changed = false; - for (size_t i = 0; i < configs.length; ) { + foreach (i, ref c; configs) { + if (c == Vertex.init) continue; // ignore deleted configurations if (!isReachableByAllParentPacks(i)) { - logDebug("NOT REACHABLE by (%s):", parents[configs[i].pack]); + logDebug("%s %s NOT REACHABLE by all of (%s):", c.pack, c.config, parents[c.pack]); removeConfig(i); changed = true; - } else i++; + } } // when all edges are cleaned up, pick one package and remove all but one config if (!changed) { foreach (p; getTopologicalPackageList()) { size_t cnt = 0; - for (size_t i = 0; i < configs.length; ) { - if (configs[i].pack == p.name) { - if (++cnt > 1) { - logDebug("NON-PRIMARY:"); - removeConfig(i); - } else i++; - } else i++; - } + foreach (i, ref c; configs) + if (c.pack == p.name && ++cnt > 1) { + logDebug("NON-PRIMARY: %s %s", c.pack, c.config); + removeConfig(i); + } if (cnt > 1) { changed = true; break; @@ -516,6 +591,7 @@ // return the resulting configuration set as an AA string[string] ret; foreach (c; configs) { + if (c == Vertex.init) continue; // ignore deleted configurations assert(ret.get(c.pack, c.config) == c.config, format("Conflicting configurations for %s found: %s vs. %s", c.pack, c.config, ret[c.pack])); logDebug("Using configuration '%s' for %s", c.config, c.pack); ret[c.pack] = c.config; @@ -542,16 +618,16 @@ * * Params: * dst = The BuildSettings struct to fill with data. - * platform = The platform to retrieve the values for. + * gsettings = The generator settings to retrieve the values for. * config = Values of the given configuration will be retrieved. * root_package = If non null, use it instead of the project's real root package. * shallow = If true, collects only build settings for the main package (including inherited settings) and doesn't stop on target type none and sourceLibrary. */ - void addBuildSettings(ref BuildSettings dst, in BuildPlatform platform, string config, in Package root_package = null, bool shallow = false) + void addBuildSettings(ref BuildSettings dst, in GeneratorSettings gsettings, string config, in Package root_package = null, bool shallow = false) const { import dub.internal.utils : stripDlangSpecialChars; - auto configs = getPackageConfigs(platform, config); + auto configs = getPackageConfigs(gsettings.platform, config); foreach (pkg; this.getTopologicalPackageList(false, root_package, configs)) { auto pkg_path = pkg.path.toNativeString(); @@ -560,11 +636,11 @@ assert(pkg.name in configs, "Missing configuration for "~pkg.name); logDebug("Gathering build settings for %s (%s)", pkg.name, configs[pkg.name]); - auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); + auto psettings = pkg.getBuildSettings(gsettings.platform, configs[pkg.name]); if (psettings.targetType != TargetType.none) { if (shallow && pkg !is m_rootPackage) psettings.sourceFiles = null; - processVars(dst, this, pkg, psettings); + processVars(dst, this, pkg, psettings, gsettings); if (psettings.importPaths.empty) logWarn(`Package %s (configuration "%s") defines no import paths, use {"importPaths": [...]} or the default package directory structure to fix this.`, pkg.name, configs[pkg.name]); if (psettings.mainSourceFile.empty && pkg is m_rootPackage && psettings.targetType == TargetType.executable) @@ -579,15 +655,15 @@ dst.targetPath = psettings.targetPath; dst.targetName = psettings.targetName; if (!psettings.workingDirectory.empty) - dst.workingDirectory = processVars(psettings.workingDirectory, this, pkg, true); + dst.workingDirectory = processVars(psettings.workingDirectory, this, pkg, gsettings, true); if (psettings.mainSourceFile.length) - dst.mainSourceFile = processVars(psettings.mainSourceFile, this, pkg, true); + dst.mainSourceFile = processVars(psettings.mainSourceFile, this, pkg, gsettings, true); } } // always add all version identifiers of all packages foreach (pkg; this.getTopologicalPackageList(false, null, configs)) { - auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); + auto psettings = pkg.getBuildSettings(gsettings.platform, configs[pkg.name]); dst.addVersions(psettings.versions); } } @@ -596,18 +672,18 @@ Params: dst = The `BuildSettings` instance to add the build settings to - platform = Target build platform + gsettings = Target generator settings build_type = Name of the build type for_root_package = Selects if the build settings are for the root package or for one of the dependencies. Unittest flags will only be added to the root package. */ - void addBuildTypeSettings(ref BuildSettings dst, in BuildPlatform platform, string build_type, bool for_root_package = true) + void addBuildTypeSettings(ref BuildSettings dst, in GeneratorSettings gsettings, bool for_root_package = true) { bool usedefflags = !(dst.requirements & BuildRequirement.noDefaultFlags); if (usedefflags) { BuildSettings btsettings; - m_rootPackage.addBuildTypeSettings(btsettings, platform, build_type); + m_rootPackage.addBuildTypeSettings(btsettings, gsettings.platform, gsettings.buildType); if (!for_root_package) { // don't propagate unittest switch to dependencies, as dependent @@ -616,7 +692,7 @@ btsettings.removeOptions(BuildOption.unittests); } - processVars(dst, this, m_rootPackage, btsettings); + processVars(dst, this, m_rootPackage, btsettings, gsettings); } } @@ -879,6 +955,7 @@ case "working-directory": case "string-import-files": case "copy-files": + case "extra-dependency-files": case "pre-generate-commands": case "post-generate-commands": case "pre-build-commands": @@ -909,6 +986,7 @@ case "linker-files": return listBuildSetting!"linkerFiles"(args); case "source-files": return listBuildSetting!"sourceFiles"(args); case "copy-files": return listBuildSetting!"copyFiles"(args); + case "extra-dependency-files": return listBuildSetting!"extraDependencyFiles"(args); case "versions": return listBuildSetting!"versions"(args); case "debug-versions": return listBuildSetting!"debugVersions"(args); case "import-paths": return listBuildSetting!"importPaths"(args); @@ -919,6 +997,8 @@ case "post-generate-commands": return listBuildSetting!"postGenerateCommands"(args); case "pre-build-commands": return listBuildSetting!"preBuildCommands"(args); case "post-build-commands": return listBuildSetting!"postBuildCommands"(args); + case "pre-run-commands": return listBuildSetting!"preRunCommands"(args); + case "post-run-commands": return listBuildSetting!"postRunCommands"(args); case "requirements": return listBuildSetting!"requirements"(args); case "options": return listBuildSetting!"options"(args); @@ -995,41 +1075,14 @@ m_selections.save(path); } - /** Checks if the cached upgrade information is still considered up to date. - - The cache will be considered out of date after 24 hours after the last - online check. - */ - bool isUpgradeCacheUpToDate() + deprecated bool isUpgradeCacheUpToDate() { - try { - auto datestr = m_packageSettings["dub"].opt!(Json[string]).get("lastUpgrade", Json("")).get!string; - if (!datestr.length) return false; - auto date = SysTime.fromISOExtString(datestr); - if ((Clock.currTime() - date) > 1.days) return false; - return true; - } catch (Exception t) { - logDebug("Failed to get the last upgrade time: %s", t.msg); - return false; - } + return false; } - /** Returns the currently cached upgrade information. - - The returned dictionary maps from dependency package name to the latest - available version that matches the dependency specifications. - */ - Dependency[string] getUpgradeCache() + deprecated Dependency[string] getUpgradeCache() { - try { - Dependency[string] ret; - foreach (string p, d; m_packageSettings["dub"].opt!(Json[string]).get("cachedUpgrades", Json.emptyObject)) - ret[p] = SelectedVersions.dependencyFromJson(d); - return ret; - } catch (Exception t) { - logDebug("Failed to get cached upgrades: %s", t.msg); - return null; - } + return null; } /** Sets a new set of versions for the upgrade cache. @@ -1052,6 +1105,7 @@ } private void writeDubJson() { + import std.file : exists, mkdir; // don't bother to write an empty file if( m_packageSettings.length == 0 ) return; @@ -1092,92 +1146,71 @@ } void processVars(ref BuildSettings dst, in Project project, in Package pack, - BuildSettings settings, bool include_target_settings = false) + BuildSettings settings, in GeneratorSettings gsettings, bool include_target_settings = false) { - dst.addDFlags(processVars(project, pack, settings.dflags)); - dst.addLFlags(processVars(project, pack, settings.lflags)); - dst.addLibs(processVars(project, pack, settings.libs)); - dst.addSourceFiles(processVars!true(project, pack, settings.sourceFiles, true)); - dst.addImportFiles(processVars(project, pack, settings.importFiles, true)); - dst.addStringImportFiles(processVars(project, pack, settings.stringImportFiles, true)); - dst.addCopyFiles(processVars(project, pack, settings.copyFiles, true)); - dst.addVersions(processVars(project, pack, settings.versions)); - dst.addDebugVersions(processVars(project, pack, settings.debugVersions)); - dst.addImportPaths(processVars(project, pack, settings.importPaths, true)); - dst.addStringImportPaths(processVars(project, pack, settings.stringImportPaths, true)); - dst.addPreGenerateCommands(processVars(project, pack, settings.preGenerateCommands)); - dst.addPostGenerateCommands(processVars(project, pack, settings.postGenerateCommands)); - dst.addPreBuildCommands(processVars(project, pack, settings.preBuildCommands)); - dst.addPostBuildCommands(processVars(project, pack, settings.postBuildCommands)); + dst.addDFlags(processVars(project, pack, gsettings, settings.dflags)); + dst.addLFlags(processVars(project, pack, gsettings, settings.lflags)); + dst.addLibs(processVars(project, pack, gsettings, settings.libs)); + dst.addSourceFiles(processVars!true(project, pack, gsettings, settings.sourceFiles, true)); + dst.addImportFiles(processVars(project, pack, gsettings, settings.importFiles, true)); + dst.addStringImportFiles(processVars(project, pack, gsettings, settings.stringImportFiles, true)); + dst.addCopyFiles(processVars(project, pack, gsettings, settings.copyFiles, true)); + dst.addExtraDependencyFiles(processVars(project, pack, gsettings, settings.extraDependencyFiles, true)); + dst.addVersions(processVars(project, pack, gsettings, settings.versions)); + dst.addDebugVersions(processVars(project, pack, gsettings, settings.debugVersions)); + dst.addVersionFilters(processVars(project, pack, gsettings, settings.versionFilters)); + dst.addDebugVersionFilters(processVars(project, pack, gsettings, settings.debugVersionFilters)); + dst.addImportPaths(processVars(project, pack, gsettings, settings.importPaths, true)); + dst.addStringImportPaths(processVars(project, pack, gsettings, settings.stringImportPaths, true)); + dst.addPreGenerateCommands(processVars(project, pack, gsettings, settings.preGenerateCommands)); + dst.addPostGenerateCommands(processVars(project, pack, gsettings, settings.postGenerateCommands)); + dst.addPreBuildCommands(processVars(project, pack, gsettings, settings.preBuildCommands)); + dst.addPostBuildCommands(processVars(project, pack, gsettings, settings.postBuildCommands)); + dst.addPreRunCommands(processVars(project, pack, gsettings, settings.preRunCommands)); + dst.addPostRunCommands(processVars(project, pack, gsettings, settings.postRunCommands)); dst.addRequirements(settings.requirements); dst.addOptions(settings.options); if (include_target_settings) { dst.targetType = settings.targetType; - dst.targetPath = processVars(settings.targetPath, project, pack, true); + dst.targetPath = processVars(settings.targetPath, project, pack, gsettings, true); dst.targetName = settings.targetName; if (!settings.workingDirectory.empty) - dst.workingDirectory = processVars(settings.workingDirectory, project, pack, true); + dst.workingDirectory = processVars(settings.workingDirectory, project, pack, gsettings, true); if (settings.mainSourceFile.length) - dst.mainSourceFile = processVars(settings.mainSourceFile, project, pack, true); + dst.mainSourceFile = processVars(settings.mainSourceFile, project, pack, gsettings, true); } } -private string[] processVars(bool glob = false)(in Project project, in Package pack, string[] vars, bool are_paths = false) +private string[] processVars(bool glob = false)(in Project project, in Package pack, in GeneratorSettings gsettings, string[] vars, bool are_paths = false) { auto ret = appender!(string[])(); - processVars!glob(ret, project, pack, vars, are_paths); + processVars!glob(ret, project, pack, gsettings, vars, are_paths); return ret.data; } -private void processVars(bool glob = false)(ref Appender!(string[]) dst, in Project project, in Package pack, string[] vars, bool are_paths = false) +private void processVars(bool glob = false)(ref Appender!(string[]) dst, in Project project, in Package pack, in GeneratorSettings gsettings, string[] vars, bool are_paths = false) { - foreach (var; vars) dst.put(processVars!glob(var, project, pack, are_paths)); + foreach (var; vars) dst.put(processVars!glob(var, project, pack, gsettings, are_paths)); } -private auto processVars(bool glob = false)(string var, in Project project, in Package pack, bool is_path) +private auto processVars(bool glob = false, Project, Package)(string var, in Project project, in Package pack, in GeneratorSettings gsettings, bool is_path) { - auto idx = std.string.indexOf(var, '$'); - if (idx >= 0) { - auto vres = appender!string(); - while (idx >= 0) { - if (idx+1 >= var.length) break; - if (var[idx+1] == '$') { - vres.put(var[0 .. idx+1]); - var = var[idx+2 .. $]; - } else { - vres.put(var[0 .. idx]); - var = var[idx+1 .. $]; - - size_t idx2 = 0; - while( idx2 < var.length && isIdentChar(var[idx2]) ) idx2++; - auto varname = var[0 .. idx2]; - var = var[idx2 .. $]; - - vres.put(getVariable(varname, project, pack)); - } - idx = std.string.indexOf(var, '$'); - } - vres.put(var); - var = vres.data; - } - - static if (glob) + static if (glob) assert(is_path, "can't glob something that isn't a path"); - else { - if (!is_path) - return var; - } - auto p = Path(var); - string res; + var = var.expandVars!(varName => getVariable(varName, project, pack, gsettings)); + if (!is_path) + return var; + auto p = NativePath(var); + string res; if (!p.absolute) res = (pack.path ~ p).toNativeString(); else res = p.toNativeString(); - static if (!glob) + static if (!glob) return res; - else { - // Find the unglobbed prefix and iterate from there. + else { + // Find the unglobbed prefix and iterate from there. size_t i = 0; size_t sepIdx = 0; loop: while (i < res.length) { @@ -1195,21 +1228,159 @@ .map!(de => de.name) .filter!(name => globMatch(name, res)) .array; + } +} + +/// Expand variables using `$VAR_NAME` or `${VAR_NAME}` syntax. +/// `$$` escapes itself and is expanded to a single `$`. +private string expandVars(alias expandVar)(string s) +{ + import std.functional : not; + + auto result = appender!string; + + static bool isVarChar(char c) + { + import std.ascii; + return isAlphaNum(c) || c == '_'; + } + + while (true) + { + auto pos = s.indexOf('$'); + if (pos < 0) + { + result.put(s); + return result.data; + } + result.put(s[0 .. pos]); + s = s[pos + 1 .. $]; + enforce(s.length > 0, "Variable name expected at end of string"); + switch (s[0]) + { + case '$': + result.put("$"); + s = s[1 .. $]; + break; + case '{': + pos = s.indexOf('}'); + enforce(pos >= 0, "Could not find '}' to match '${'"); + result.put(expandVar(s[1 .. pos])); + s = s[pos + 1 .. $]; + break; + default: + pos = s.representation.countUntil!(not!isVarChar); + if (pos < 0) + pos = s.length; + result.put(expandVar(s[0 .. pos])); + s = s[pos .. $]; + break; + } } } -private string getVariable(string name, in Project project, in Package pack) +unittest { - if (name == "PACKAGE_DIR") return pack.path.toNativeString(); - if (name == "ROOT_PACKAGE_DIR") return project.rootPackage.path.toNativeString(); + string[string] vars = + [ + "A" : "a", + "B" : "b", + ]; + + string expandVar(string name) { auto p = name in vars; enforce(p, name); return *p; } + + assert(expandVars!expandVar("") == ""); + assert(expandVars!expandVar("x") == "x"); + assert(expandVars!expandVar("$$") == "$"); + assert(expandVars!expandVar("x$$") == "x$"); + assert(expandVars!expandVar("$$x") == "$x"); + assert(expandVars!expandVar("$$$$") == "$$"); + assert(expandVars!expandVar("x$A") == "xa"); + assert(expandVars!expandVar("x$$A") == "x$A"); + assert(expandVars!expandVar("$A$B") == "ab"); + assert(expandVars!expandVar("${A}$B") == "ab"); + assert(expandVars!expandVar("$A${B}") == "ab"); + assert(expandVars!expandVar("a${B}") == "ab"); + assert(expandVars!expandVar("${A}b") == "ab"); + + import std.exception : assertThrown; + assertThrown(expandVars!expandVar("$")); + assertThrown(expandVars!expandVar("${}")); + assertThrown(expandVars!expandVar("$|")); + assertThrown(expandVars!expandVar("x$")); + assertThrown(expandVars!expandVar("$X")); + assertThrown(expandVars!expandVar("${")); + assertThrown(expandVars!expandVar("${X")); + + // https://github.com/dlang/dmd/pull/9275 + assert(expandVars!expandVar("$${DUB_EXE:-dub}") == "${DUB_EXE:-dub}"); +} + +// Keep the following list up-to-date if adding more build settings variables. +/// List of variables that can be used in build settings +package(dub) immutable buildSettingsVars = [ + "ARCH", "PLATFORM", "PLATFORM_POSIX", "BUILD_TYPE" +]; + +private string getVariable(Project, Package)(string name, in Project project, in Package pack, in GeneratorSettings gsettings) +{ + import dub.internal.utils : getDUBExePath; + import std.process : environment, escapeShellFileName; + import std.uni : asUpperCase; + + NativePath path; + if (name == "PACKAGE_DIR") + path = pack.path; + else if (name == "ROOT_PACKAGE_DIR") + path = project.rootPackage.path; if (name.endsWith("_PACKAGE_DIR")) { auto pname = name[0 .. $-12]; foreach (prj; project.getTopologicalPackageList()) - if (prj.name.toUpper().replace("-", "_") == pname) - return prj.path.toNativeString(); + if (prj.name.asUpperCase.map!(a => a == '-' ? '_' : a).equal(pname)) + { + path = prj.path; + break; + } } + if (!path.empty) + { + // no trailing slash for clean path concatenation (see #1392) + path.endsWithSlash = false; + return path.toNativeString(); + } + + if (name == "DUB") { + return getDUBExePath(gsettings.platform.compilerBinary); + } + + if (name == "ARCH") { + foreach (a; gsettings.platform.architecture) + return a; + return ""; + } + + if (name == "PLATFORM") { + import std.algorithm : filter; + foreach (p; gsettings.platform.platform.filter!(p => p != "posix")) + return p; + foreach (p; gsettings.platform.platform) + return p; + return ""; + } + + if (name == "PLATFORM_POSIX") { + import std.algorithm : canFind; + if (gsettings.platform.platform.canFind("posix")) + return "posix"; + foreach (p; gsettings.platform.platform) + return p; + return ""; + } + + if (name == "BUILD_TYPE") return gsettings.buildType; + auto envvar = environment.get(name); if (envvar !is null) return envvar; @@ -1217,6 +1388,68 @@ } +unittest +{ + static struct MockPackage + { + this(string name) + { + this.name = name; + version (Posix) + path = NativePath("/pkgs/"~name); + else version (Windows) + path = NativePath(`C:\pkgs\`~name); + // see 4d4017c14c, #268, and #1392 for why this all package paths end on slash internally + path.endsWithSlash = true; + } + string name; + NativePath path; + } + + static struct MockProject + { + MockPackage rootPackage; + inout(MockPackage)[] getTopologicalPackageList() inout + { + return _dependencies; + } + private: + MockPackage[] _dependencies; + } + + MockProject proj = { + rootPackage: MockPackage("root"), + _dependencies: [MockPackage("dep1"), MockPackage("dep2")] + }; + auto pack = MockPackage("test"); + GeneratorSettings gsettings; + enum isPath = true; + + import std.path : dirSeparator; + + static Path woSlash(Path p) { p.endsWithSlash = false; return p; } + // basic vars + assert(processVars("Hello $PACKAGE_DIR", proj, pack, gsettings, !isPath) == "Hello "~woSlash(pack.path).toNativeString); + assert(processVars("Hello $ROOT_PACKAGE_DIR", proj, pack, gsettings, !isPath) == "Hello "~woSlash(proj.rootPackage.path).toNativeString.chomp(dirSeparator)); + assert(processVars("Hello $DEP1_PACKAGE_DIR", proj, pack, gsettings, !isPath) == "Hello "~woSlash(proj._dependencies[0].path).toNativeString); + // ${VAR} replacements + assert(processVars("Hello ${PACKAGE_DIR}"~dirSeparator~"foobar", proj, pack, gsettings, !isPath) == "Hello "~(pack.path ~ "foobar").toNativeString); + assert(processVars("Hello $PACKAGE_DIR"~dirSeparator~"foobar", proj, pack, gsettings, !isPath) == "Hello "~(pack.path ~ "foobar").toNativeString); + // test with isPath + assert(processVars("local", proj, pack, gsettings, isPath) == (pack.path ~ "local").toNativeString); + assert(processVars("foo/$$ESCAPED", proj, pack, gsettings, isPath) == (pack.path ~ "foo/$ESCAPED").toNativeString); + assert(processVars("$$ESCAPED", proj, pack, gsettings, !isPath) == "$ESCAPED"); + // test other env variables + import std.process : environment; + environment["MY_ENV_VAR"] = "blablabla"; + assert(processVars("$MY_ENV_VAR", proj, pack, gsettings, !isPath) == "blablabla"); + assert(processVars("${MY_ENV_VAR}suffix", proj, pack, gsettings, !isPath) == "blablablasuffix"); + assert(processVars("$MY_ENV_VAR-suffix", proj, pack, gsettings, !isPath) == "blablabla-suffix"); + assert(processVars("$MY_ENV_VAR:suffix", proj, pack, gsettings, !isPath) == "blablabla:suffix"); + assert(processVars("$MY_ENV_VAR$MY_ENV_VAR", proj, pack, gsettings, !isPath) == "blablablablablabla"); + environment.remove("MY_ENV_VAR"); +} + /** Holds and stores a set of version selections for package dependencies. This is the runtime representation of the information contained in @@ -1253,7 +1486,7 @@ /** Constructs a new version selections from an existing JSON file. */ - this(Path path) + this(NativePath path) { auto json = jsonFromFile(path); deserialize(json); @@ -1296,7 +1529,7 @@ } /// Selects a certain path for a specific package. - void selectVersion(string package_id, Path path) + void selectVersion(string package_id, NativePath path) { if (auto ps = package_id in m_selections) { if (ps.dep == Dependency(path)) @@ -1338,7 +1571,7 @@ should be used as the file name and the directory should be the root directory of the project's root package. */ - void save(Path path) + void save(NativePath path) { Json json = serialize(); auto file = openFile(path, FileMode.createTrunc); @@ -1377,7 +1610,7 @@ if (j.type == Json.Type.string) return Dependency(Version(j.get!string)); else if (j.type == Json.Type.object) - return Dependency(Path(j["path"].get!string)); + return Dependency(NativePath(j["path"].get!string)); else throw new Exception(format("Unexpected type for dependency: %s", j.type)); } @@ -1401,4 +1634,3 @@ m_selections[p] = Selected(dependencyFromJson(v)); } } - diff --git a/source/dub/recipe/io.d b/source/dub/recipe/io.d index 9704931..c66072e 100644 --- a/source/dub/recipe/io.d +++ b/source/dub/recipe/io.d @@ -16,7 +16,7 @@ The file format (JSON/SDLang) will be determined from the file extension. Params: - filename = Path of the package recipe file + filename = NativePath of the package recipe file parent_name = Optional name of the parent package (if this is a sub package) Returns: Returns the package recipe contents @@ -24,10 +24,10 @@ */ PackageRecipe readPackageRecipe(string filename, string parent_name = null) { - return readPackageRecipe(Path(filename), parent_name); + return readPackageRecipe(NativePath(filename), parent_name); } /// ditto -PackageRecipe readPackageRecipe(Path filename, string parent_name = null) +PackageRecipe readPackageRecipe(NativePath filename, string parent_name = null) { import dub.internal.utils : stripUTF8Bom; import dub.internal.vibecompat.core.file : openFile, FileMode; @@ -53,11 +53,14 @@ to determine the file format from the file extension parent_name = Optional name of the parent package (if this is a sub package) + default_package_name = Optional default package name (if no package name + is found in the recipe this value will be used) Returns: Returns the package recipe contents Throws: Throws an exception if an I/O or syntax error occurs */ -PackageRecipe parsePackageRecipe(string contents, string filename, string parent_name = null) +PackageRecipe parsePackageRecipe(string contents, string filename, string parent_name = null, + string default_package_name = null) { import std.algorithm : endsWith; import dub.internal.vibecompat.data.json; @@ -66,6 +69,8 @@ PackageRecipe ret; + ret.name = default_package_name; + if (filename.endsWith(".json")) parseJson(ret, parseJsonString(contents, filename), parent_name); else if (filename.endsWith(".sdl")) parseSDL(ret, contents, parent_name, filename); else assert(false, "readPackageRecipe called with filename with unknown extension: "~filename); @@ -132,7 +137,7 @@ } /// ditto -void writePackageRecipe(Path filename, in ref PackageRecipe recipe) +void writePackageRecipe(NativePath filename, in ref PackageRecipe recipe) { writePackageRecipe(filename.toNativeString, recipe); } diff --git a/source/dub/recipe/json.d b/source/dub/recipe/json.d index a254e89..354a1e6 100644 --- a/source/dub/recipe/json.d +++ b/source/dub/recipe/json.d @@ -41,6 +41,9 @@ recipe.buildTypes[name] = bs; } break; + case "toolchainRequirements": + recipe.toolchainRequirements.parseJson(value); + break; case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break; case "-ddoxTool": recipe.ddoxTool = value.get!string; break; } @@ -103,6 +106,9 @@ types[name] = settings.toJson(); ret["buildTypes"] = types; } + if (!recipe.toolchainRequirements.empty) { + ret["toolchainRequirements"] = recipe.toolchainRequirements.toJson(); + } if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson(); if (!recipe.ddoxTool.empty) ret["-ddoxTool"] = recipe.ddoxTool; return ret; @@ -212,14 +218,19 @@ case "sourcePath": bs.sourcePaths[suffix] ~= [value.get!string]; break; // deprecated case "excludedSourceFiles": bs.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break; case "copyFiles": bs.copyFiles[suffix] = deserializeJson!(string[])(value); break; + case "extraDependencyFiles": bs.extraDependencyFiles[suffix] = deserializeJson!(string[])(value); break; case "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break; case "debugVersions": bs.debugVersions[suffix] = deserializeJson!(string[])(value); break; + case "-versionFilters": bs.versionFilters[suffix] = deserializeJson!(string[])(value); break; + case "-debugVersionFilters": bs.debugVersionFilters[suffix] = deserializeJson!(string[])(value); break; case "importPaths": bs.importPaths[suffix] = deserializeJson!(string[])(value); break; case "stringImportPaths": bs.stringImportPaths[suffix] = deserializeJson!(string[])(value); break; case "preGenerateCommands": bs.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break; case "postGenerateCommands": bs.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break; case "preBuildCommands": bs.preBuildCommands[suffix] = deserializeJson!(string[])(value); break; case "postBuildCommands": bs.postBuildCommands[suffix] = deserializeJson!(string[])(value); break; + case "preRunCommands": bs.preRunCommands[suffix] = deserializeJson!(string[])(value); break; + case "postRunCommands": bs.postRunCommands[suffix] = deserializeJson!(string[])(value); break; case "buildRequirements": BuildRequirements reqs; foreach (req; deserializeJson!(string[])(value)) @@ -259,14 +270,19 @@ foreach (suffix, arr; bs.sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.extraDependencyFiles) ret["extraDependencyFiles"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.versions) ret["versions"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.versionFilters) ret["-versionFilters"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.debugVersionFilters) ret["-debugVersionFilters"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.importPaths) ret["importPaths"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.preRunCommands) ret["preRunCommands"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.postRunCommands) ret["postRunCommands"~suffix] = serializeToJson(arr); foreach (suffix, arr; bs.buildRequirements) { string[] val; foreach (i; [EnumMembers!BuildRequirement]) @@ -281,3 +297,20 @@ } return ret; } + +private void parseJson(ref ToolchainRequirements tr, Json json) +{ + foreach (string name, value; json) + tr.addRequirement(name, value.get!string); +} + +private Json toJson(in ref ToolchainRequirements tr) +{ + auto ret = Json.emptyObject; + if (tr.dub != Dependency.any) ret["dub"] = serializeToJson(tr.dub); + if (tr.frontend != Dependency.any) ret["frontend"] = serializeToJson(tr.frontend); + if (tr.dmd != Dependency.any) ret["dmd"] = serializeToJson(tr.dmd); + if (tr.ldc != Dependency.any) ret["ldc"] = serializeToJson(tr.ldc); + if (tr.gdc != Dependency.any) ret["gdc"] = serializeToJson(tr.gdc); + return ret; +} diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index 3ab9c66..5a73030 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -20,6 +20,7 @@ import std.exception : enforce; import std.file; import std.range; +import std.process : environment; /** @@ -27,9 +28,9 @@ Sub qualified package names are lists of package names separated by ":". For example, "packa:packb:packc" references a package named "packc" that is a - sub package of "packb", wich in turn is a sub package of "packa". + sub package of "packb", which in turn is a sub package of "packa". */ -string[] getSubPackagePath(string package_name) +string[] getSubPackagePath(string package_name) @safe pure { return package_name.split(":"); } @@ -39,7 +40,7 @@ In case of a top level package, the qualified name is returned unmodified. */ -string getBasePackageName(string package_name) +string getBasePackageName(string package_name) @safe pure { return package_name.findSplit(":")[0]; } @@ -50,12 +51,12 @@ This is the part of the package name excluding the base package name. See also $(D getBasePackageName). */ -string getSubPackageName(string package_name) +string getSubPackageName(string package_name) @safe pure { return package_name.findSplit(":")[2]; } -unittest +@safe unittest { assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]); assert(getSubPackagePath("pack") == ["pack"]); @@ -85,6 +86,8 @@ ConfigurationInfo[] configurations; BuildSettingsTemplate[string] buildTypes; + ToolchainRequirements toolchainRequirements; + SubPackage[] subPackages; inout(ConfigurationInfo) getConfiguration(string name) @@ -106,6 +109,44 @@ PackageRecipe recipe; } +/// Describes minimal toolchain requirements +struct ToolchainRequirements +{ + import std.typecons : Tuple, tuple; + + /// DUB version requirement + Dependency dub = Dependency.any; + /// D front-end version requirement + Dependency frontend = Dependency.any; + /// DMD version requirement + Dependency dmd = Dependency.any; + /// LDC version requirement + Dependency ldc = Dependency.any; + /// GDC version requirement + Dependency gdc = Dependency.any; + + /** Get the list of supported compilers. + + Returns: + An array of couples of compiler name and compiler requirement + */ + @property Tuple!(string, Dependency)[] supportedCompilers() const + { + Tuple!(string, Dependency)[] res; + if (dmd != Dependency.invalid) res ~= Tuple!(string, Dependency)("dmd", dmd); + if (ldc != Dependency.invalid) res ~= Tuple!(string, Dependency)("ldc", ldc); + if (gdc != Dependency.invalid) res ~= Tuple!(string, Dependency)("gdc", gdc); + return res; + } + + bool empty() + const { + import std.algorithm.searching : all; + return only(dub, frontend, dmd, ldc, gdc) + .all!(r => r == Dependency.any); + } +} + /// Bundles information about a build configuration. struct ConfigurationInfo { @@ -149,63 +190,87 @@ string[][string] sourcePaths; string[][string] excludedSourceFiles; string[][string] copyFiles; + string[][string] extraDependencyFiles; string[][string] versions; string[][string] debugVersions; + string[][string] versionFilters; + string[][string] debugVersionFilters; string[][string] importPaths; string[][string] stringImportPaths; string[][string] preGenerateCommands; string[][string] postGenerateCommands; string[][string] preBuildCommands; string[][string] postBuildCommands; + string[][string] preRunCommands; + string[][string] postRunCommands; BuildRequirements[string] buildRequirements; BuildOptions[string] buildOptions; /// Constructs a BuildSettings object from this template. - void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path base_path) + void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path) const { dst.targetType = this.targetType; if (!this.targetPath.empty) dst.targetPath = this.targetPath; if (!this.targetName.empty) dst.targetName = this.targetName; if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory; if (!this.mainSourceFile.empty) { - dst.mainSourceFile = this.mainSourceFile; - dst.addSourceFiles(this.mainSourceFile); + auto p = NativePath(this.mainSourceFile); + p.normalize(); + dst.mainSourceFile = p.toNativeString(); + dst.addSourceFiles(dst.mainSourceFile); } - void collectFiles(string method)(in string[][string] paths_map, string pattern) + string[] collectFiles(in string[][string] paths_map, string pattern) { + auto files = appender!(string[]); + + import dub.project : buildSettingsVars; + auto envVars = environment.toAA(); + foreach (suffix, paths; paths_map) { if (!platform.matchesSpecification(suffix)) continue; foreach (spath; paths) { enforce(!spath.empty, "Paths must not be empty strings."); - auto path = Path(spath); + auto path = NativePath(spath); if (!path.absolute) path = base_path ~ path; if (!existsFile(path) || !isDir(path.toNativeString())) { - logWarn("Invalid source/import path: %s", path.toNativeString()); + import std.algorithm : any, find; + const hasVar = chain(buildSettingsVars, envVars.byKey).any!((string var) { + return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0; + }); + if (!hasVar) + logWarn("Invalid source/import path: %s", path.toNativeString()); continue; } foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { import std.path : baseName; - if (baseName(d.name)[0] == '.' || isDir(d.name)) continue; - auto src = Path(d.name).relativeTo(base_path); - __traits(getMember, dst, method)(src.toNativeString()); + if (baseName(d.name)[0] == '.' || d.isDir) continue; + auto src = NativePath(d.name).relativeTo(base_path); + files ~= src.toNativeString(); } } } + + return files.data; } - // collect files from all source/import folders - collectFiles!"addSourceFiles"(sourcePaths, "*.d"); - collectFiles!"addImportFiles"(importPaths, "*.{d,di}"); - dst.removeImportFiles(dst.sourceFiles); - collectFiles!"addStringImportFiles"(stringImportPaths, "*"); + // collect source files + dst.addSourceFiles(collectFiles(sourcePaths, "*.d")); + auto sourceFiles = dst.sourceFiles.sort(); - // ensure a deterministic order of files as passed to the compiler - dst.sourceFiles.sort(); + // collect import files and remove sources + import std.algorithm : copy, setDifference; + + auto importFiles = collectFiles(importPaths, "*.{d,di}").sort(); + immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length; + importFiles = importFiles[0 .. $ - nremoved]; + dst.addImportFiles(importFiles.release); + + dst.addStringImportFiles(collectFiles(stringImportPaths, "*")); getPlatformSetting!("dflags", "addDFlags")(dst, platform); getPlatformSetting!("lflags", "addLFlags")(dst, platform); @@ -213,14 +278,19 @@ getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); + getPlatformSetting!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform); getPlatformSetting!("versions", "addVersions")(dst, platform); getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform); + getPlatformSetting!("versionFilters", "addVersionFilters")(dst, platform); + getPlatformSetting!("debugVersionFilters", "addDebugVersionFilters")(dst, platform); getPlatformSetting!("importPaths", "addImportPaths")(dst, platform); getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform); getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform); getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform); getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform); getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform); + getPlatformSetting!("preRunCommands", "addPreRunCommands")(dst, platform); + getPlatformSetting!("postRunCommands", "addPostRunCommands")(dst, platform); getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform); getPlatformSetting!("buildOptions", "addOptions")(dst, platform); } @@ -260,6 +330,103 @@ } } +package(dub) void checkPlatform(in ref ToolchainRequirements tr, BuildPlatform platform, string package_name) +{ + import dub.compilers.utils : dmdLikeVersionToSemverLike; + import std.algorithm.iteration : map; + import std.format : format; + + string compilerver; + Dependency compilerspec; + + switch (platform.compiler) { + default: + compilerspec = Dependency.any; + compilerver = "0.0.0"; + break; + case "dmd": + compilerspec = tr.dmd; + compilerver = platform.compilerVersion.length + ? dmdLikeVersionToSemverLike(platform.compilerVersion) + : "0.0.0"; + break; + case "ldc": + compilerspec = tr.ldc; + compilerver = platform.compilerVersion; + if (!compilerver.length) compilerver = "0.0.0"; + break; + case "gdc": + compilerspec = tr.gdc; + compilerver = platform.compilerVersion; + if (!compilerver.length) compilerver = "0.0.0"; + break; + } + + enforce(compilerspec != Dependency.invalid, + format( + "Installed %s %s is not supported by %s. Supported compiler(s):\n%s", + platform.compiler, platform.compilerVersion, package_name, + tr.supportedCompilers.map!((cs) { + auto str = " - " ~ cs[0]; + if (cs[1] != Dependency.any) str ~= ": " ~ cs[1].toString(); + return str; + }).join("\n") + ) + ); + + enforce(compilerspec.matches(compilerver), + format( + "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~ + "Please consider upgrading your installation.", + platform.compiler, platform.compilerVersion, + package_name, platform.compiler, compilerspec + ) + ); + + enforce(tr.frontend.matches(dmdLikeVersionToSemverLike(platform.frontendVersionString)), + format( + "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~ + "Please consider upgrading your installation.", + platform.compiler, platform.compilerVersion, + platform.frontendVersionString, package_name, tr.frontend + ) + ); +} + +package bool addRequirement(ref ToolchainRequirements req, string name, string value) +{ + switch (name) { + default: return false; + case "dub": req.dub = parseDependency(value); break; + case "frontend": req.frontend = parseDMDDependency(value); break; + case "ldc": req.ldc = parseDependency(value); break; + case "gdc": req.gdc = parseDependency(value); break; + case "dmd": req.dmd = parseDMDDependency(value); break; + } + return true; +} + +private static Dependency parseDependency(string dep) +{ + if (dep == "no") return Dependency.invalid; + return Dependency(dep); +} + +private static Dependency parseDMDDependency(string dep) +{ + import dub.compilers.utils : dmdLikeVersionToSemverLike; + import dub.dependency : Dependency; + import std.algorithm : map, splitter; + import std.array : join; + + if (dep == "no") return Dependency.invalid; + return dep + .splitter(' ') + .map!(r => dmdLikeVersionToSemverLike(r)) + .join(' ') + .Dependency; +} + private T clone(T)(ref const(T) val) { import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType; @@ -288,3 +455,23 @@ return ret; } else static assert(false, "Unsupported type: "~T.stringof); } + +unittest { // issue #1407 - duplicate main source file + { + BuildSettingsTemplate t; + t.mainSourceFile = "./foo.d"; + t.sourceFiles[""] = ["foo.d"]; + BuildSettings bs; + t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/")); + assert(bs.sourceFiles == ["foo.d"]); + } + + version (Windows) {{ + BuildSettingsTemplate t; + t.mainSourceFile = "src/foo.d"; + t.sourceFiles[""] = ["src\\foo.d"]; + BuildSettings bs; + t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/")); + assert(bs.sourceFiles == ["src\\foo.d"]); + }} +} diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index fc4c33f..09e8b8d 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -50,6 +50,9 @@ parseBuildSettings(n, bt, parent_name); recipe.buildTypes[name] = bt; break; + case "toolchainRequirements": + parseToolchainRequirements(recipe.toolchainRequirements, n); + break; case "x:ddoxFilterArgs": recipe.ddoxFilterArgs ~= n.stringArrayTagValue; break; case "x:ddoxTool": recipe.ddoxTool = n.stringTagValue; break; } @@ -101,6 +104,9 @@ t.add(settings.toSDL()); ret.add(t); } + if (!recipe.toolchainRequirements.empty) { + ret.add(toSDL(recipe.toolchainRequirements)); + } if (recipe.ddoxFilterArgs.length) ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array)); if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)])); @@ -121,7 +127,7 @@ private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, string package_name) { - foreach (setting; settings.tags) + foreach (setting; settings.all.tags) parseBuildSetting(setting, bs, package_name); } @@ -148,14 +154,19 @@ case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break; case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break; case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break; + case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break; case "versions": setting.parsePlatformStringArray(bs.versions); break; case "debugVersions": setting.parsePlatformStringArray(bs.debugVersions); break; + case "x:versionFilters": setting.parsePlatformStringArray(bs.versionFilters); break; + case "x:debugVersionFilters": setting.parsePlatformStringArray(bs.debugVersionFilters); break; case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break; case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break; case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break; case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break; case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break; case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break; + case "preRunCommands": setting.parsePlatformStringArray(bs.preRunCommands); break; + case "postRunCommands": setting.parsePlatformStringArray(bs.postRunCommands); break; case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirement(bs.buildRequirements); break; case "buildOptions": setting.parsePlatformEnumArray!BuildOption(bs.buildOptions); break; } @@ -177,7 +188,7 @@ if ("version" in attrs) logDiagnostic("Ignoring version specification (%s) for path based dependency %s", attrs["version"][0].value.get!string, attrs["path"][0].value.get!string); dep.versionSpec = "*"; - dep.path = Path(attrs["path"][0].value.get!string); + dep.path = NativePath(attrs["path"][0].value.get!string); } else { enforceSDL("version" in attrs, "Missing version specification.", t); dep.versionSpec = attrs["version"][0].value.get!string; @@ -214,9 +225,9 @@ private Tag[] toSDL(in ref BuildSettingsTemplate bs) { Tag[] ret; - void add(string name, string value) { ret ~= new Tag(null, name, [Value(value)]); } - void adda(string name, string suffix, in string[] values) { - ret ~= new Tag(null, name, values[].map!(v => Value(v)).array, + void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); } + void adda(string name, string suffix, in string[] values, string namespace = null) { + ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array, suffix.length ? [new Attribute(null, "platform", Value(suffix[1 .. $]))] : null); } @@ -227,10 +238,10 @@ ret ~= m; return ret; } - + foreach (pack, d; bs.dependencies) { Attribute[] attribs; - if (d.path.length) attribs ~= new Attribute(null, "path", Value(d.path.toString())); + if (!d.path.empty) attribs ~= new Attribute(null, "path", Value(d.path.toString())); else attribs ~= new Attribute(null, "version", Value(d.versionSpec)); if (d.optional) attribs ~= new Attribute(null, "optional", Value(true)); ret ~= new Tag(null, "dependency", [Value(pack)], attribs); @@ -249,19 +260,41 @@ foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr); foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr); foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr); + foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr); foreach (suffix, arr; bs.versions) adda("versions", suffix, arr); foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr); + foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x"); + foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x"); foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr); foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr); foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr); foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr); foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr); foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr); + foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr); + foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr); foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits)); foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits)); return ret; } +private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag) +{ + foreach (attr; tag.attributes) + tr.addRequirement(attr.name, attr.value.get!string); +} + +private Tag toSDL(const ref ToolchainRequirements tr) +{ + Attribute[] attrs; + if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString())); + if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString())); + if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString())); + if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString())); + if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString())); + return new Tag(null, "toolchainRequirements", null, attrs); +} + private string expandPackageName(string name, string parent_name, Tag tag) { import std.algorithm : canFind; @@ -359,6 +392,7 @@ buildType "release" { dflags "-release" "-O" } +toolchainRequirements dub="~>1.11.0" dmd="~>2.082" x:ddoxFilterArgs "-arg1" "-arg2" x:ddoxFilterArgs "-arg3" x:ddoxTool "ddoxtool" @@ -384,10 +418,18 @@ mainSourceFile "main source" copyFiles "copy1" "copy2" copyFiles "copy3" +extraDependencyFiles "extradepfile1" "extradepfile2" +extraDependencyFiles "extradepfile3" versions "version1" "version2" versions "version3" debugVersions "debug1" "debug2" debugVersions "debug3" +x:versionFilters "version1" "version2" +x:versionFilters "version3" +x:versionFilters +x:debugVersionFilters "debug1" "debug2" +x:debugVersionFilters "debug3" +x:debugVersionFilters importPaths "import1" "import2" importPaths "import3" stringImportPaths "string1" "string2" @@ -400,6 +442,10 @@ preBuildCommands "preb3" postBuildCommands "postb1" "postb2" postBuildCommands "postb3" +preRunCommands "prer1" "prer2" +preRunCommands "prer3" +postRunCommands "postr1" "postr2" +postRunCommands "postr3" dflags "df1" "df2" dflags "df3" lflags "lf1" "lf2" @@ -435,11 +481,16 @@ assert(rec.buildTypes.length == 2); assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]); assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]); + assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0")); + assert(rec.toolchainRequirements.frontend == Dependency.any); + assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0")); + assert(rec.toolchainRequirements.ldc == Dependency.any); + assert(rec.toolchainRequirements.gdc == Dependency.any); assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string); assert(rec.ddoxTool == "ddoxtool"); assert(rec.buildSettings.dependencies.length == 2); assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false); - assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == Path(".")); + assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath(".")); assert(rec.buildSettings.dependencies["somedep"].versionSpec == "1.0.0"); assert(rec.buildSettings.dependencies["somedep"].optional == true); assert(rec.buildSettings.dependencies["somedep"].path.empty); @@ -458,14 +509,19 @@ assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]); assert(rec.buildSettings.mainSourceFile == "main source"); assert(rec.buildSettings.copyFiles == ["": ["copy1", "copy2", "copy3"]]); + assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]); assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]); assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]); + assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]); + assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]); assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]); assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]); assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]); assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]); assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]); assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]); + assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]); + assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]); assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]); assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]); } @@ -518,7 +574,7 @@ p.buildSettings.dflags["-windows"] = ["-a"]; p.buildSettings.lflags[""] = ["-b", "-c"]; auto sdl = toSDL(p).toSDLDocument(); - assert(sdl == + assert(sdl == `name "test" authors "foo" "bar" dflags "-a" platform="windows" diff --git a/source/dub/semver.d b/source/dub/semver.d index 448ac81..dd68102 100644 --- a/source/dub/semver.d +++ b/source/dub/semver.d @@ -20,12 +20,13 @@ import std.algorithm : max; import std.conv; +@safe: /** Validates a version string according to the SemVer specification. */ bool isValidVersion(string ver) -{ +pure @nogc { // NOTE: this is not by spec, but to ensure sane input if (ver.length > 256) return false; @@ -101,7 +102,7 @@ /** Determines if a given valid SemVer version has a pre-release suffix. */ -bool isPreReleaseVersion(string ver) +bool isPreReleaseVersion(string ver) pure @nogc in { assert(isValidVersion(ver)); } body { foreach (i; 0 .. 2) { @@ -135,14 +136,14 @@ equal, and a positive number otherwise. */ int compareVersions(string a, string b) -{ +pure @nogc { // compare a.b.c numerically if (auto ret = compareNumber(a, b)) return ret; assert(a[0] == '.' && b[0] == '.'); - a.popFront(); b.popFront(); + a = a[1 .. $]; b = b[1 .. $]; if (auto ret = compareNumber(a, b)) return ret; assert(a[0] == '.' && b[0] == '.'); - a.popFront(); b.popFront(); + a = a[1 .. $]; b = b[1 .. $]; if (auto ret = compareNumber(a, b)) return ret; // give precedence to non-prerelease versions @@ -153,7 +154,7 @@ // compare the prerelease tail lexicographically do { - a.popFront(); b.popFront(); + a = a[1 .. $]; b = b[1 .. $]; if (auto ret = compareIdentifier(a, b)) return ret; } while (a.length > 0 && b.length > 0 && a[0] != '+' && b[0] != '+'); @@ -224,12 +225,13 @@ See_Also: `expandVersion` */ -string bumpVersion(string ver) { +string bumpVersion(string ver) +pure { // Cut off metadata and prerelease information. auto mi = ver.indexOfAny("+-"); if (mi > 0) ver = ver[0..mi]; // Increment next to last version from a[.b[.c]]. - auto splitted = split(ver, "."); + auto splitted = () @trusted { return split(ver, "."); } (); // DMD 2.065.0 assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver); auto to_inc = splitted.length == 3? 1 : 0; splitted = splitted[0 .. to_inc+1]; @@ -251,20 +253,21 @@ /** Takes a partial version and expands it to a valid SemVer version. - + This function corresponds to the semantivs of the "~>" comparison operator's lower bound. See_Also: `bumpVersion` */ -string expandVersion(string ver) { +string expandVersion(string ver) +pure { auto mi = ver.indexOfAny("+-"); auto sub = ""; if (mi > 0) { sub = ver[mi..$]; ver = ver[0..mi]; } - auto splitted = split(ver, "."); + auto splitted = () @trusted { return split(ver, "."); } (); // DMD 2.065.0 assert(splitted.length > 0 && splitted.length <= 3, "Version corrupt: " ~ ver); while (splitted.length < 3) splitted ~= "0"; return splitted.join(".") ~ sub; @@ -281,18 +284,18 @@ } private int compareIdentifier(ref string a, ref string b) -{ +pure @nogc { bool anumber = true; bool bnumber = true; bool aempty = true, bempty = true; int res = 0; while (true) { - if (a.front != b.front && res == 0) res = a.front - b.front; - if (anumber && (a.front < '0' || a.front > '9')) anumber = false; - if (bnumber && (b.front < '0' || b.front > '9')) bnumber = false; - a.popFront(); b.popFront(); - aempty = a.empty || a.front == '.' || a.front == '+'; - bempty = b.empty || b.front == '.' || b.front == '+'; + if (a[0] != b[0] && res == 0) res = a[0] - b[0]; + if (anumber && (a[0] < '0' || a[0] > '9')) anumber = false; + if (bnumber && (b[0] < '0' || b[0] > '9')) bnumber = false; + a = a[1 .. $]; b = b[1 .. $]; + aempty = !a.length || a[0] == '.' || a[0] == '+'; + bempty = !b.length || b[0] == '.' || b[0] == '+'; if (aempty || bempty) break; } @@ -311,20 +314,20 @@ } private int compareNumber(ref string a, ref string b) -{ +pure @nogc { int res = 0; while (true) { - if (a.front != b.front && res == 0) res = a.front - b.front; - a.popFront(); b.popFront(); - auto aempty = a.empty || (a.front < '0' || a.front > '9'); - auto bempty = b.empty || (b.front < '0' || b.front > '9'); + if (a[0] != b[0] && res == 0) res = a[0] - b[0]; + a = a[1 .. $]; b = b[1 .. $]; + auto aempty = !a.length || (a[0] < '0' || a[0] > '9'); + auto bempty = !b.length || (b[0] < '0' || b[0] > '9'); if (aempty != bempty) return bempty - aempty; if (aempty) return res; } } private bool isValidIdentifierChain(string str, bool allow_leading_zeros = false) -{ +pure @nogc { if (str.length == 0) return false; while (str.length) { auto end = str.indexOf('.'); @@ -337,7 +340,7 @@ } private bool isValidIdentifier(string str, bool allow_leading_zeros = false) -{ +pure @nogc { if (str.length < 1) return false; bool numeric = true; @@ -360,7 +363,7 @@ } private bool isValidNumber(string str) -{ +pure @nogc { if (str.length < 1) return false; foreach (ch; str) if (ch < '0' || ch > '9') @@ -373,7 +376,7 @@ } private sizediff_t indexOfAny(string str, in char[] chars) -{ +pure @nogc { sizediff_t ret = -1; foreach (ch; chars) { auto idx = str.indexOf(ch); diff --git a/source/dub/version_.d b/source/dub/version_.d index 613930c..bbf0692 100644 --- a/source/dub/version_.d +++ b/source/dub/version_.d @@ -1,2 +1,2 @@ module dub.version_; -enum dubVersion = "v1.1.0"; +enum dubVersion = "v1.16.0-beta.1"; diff --git a/test/.gitignore b/test/.gitignore index f58740b..ff95cca 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -12,3 +12,5 @@ path-subpackage-ref/test subpackage-ref/test subpackage-common-with-sourcefile-globbing/mypackage* + +/test_registry diff --git a/test/0-init-fail-json.sh b/test/0-init-fail-json.sh index 63ebb5a..069014e 100755 --- a/test/0-init-fail-json.sh +++ b/test/0-init-fail-json.sh @@ -1,9 +1,13 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-fail-pack" deps="logger PACKAGE_DONT_EXIST" # would be very unlucky if it does exist... -$DUB init -n $packname $deps -f json +if $$DUB init -n $packname $deps -f json 2>/dev/null; then + die $LINENO 'Init with unknown non-existing dependency expected to fail' +fi + function cleanup { rm -rf $packname @@ -11,6 +15,5 @@ if [ -e $packname/dub.json ]; then # package is there, it should have failed cleanup - exit 1 + die $LINENO "$packname/dub.json was not created" fi -exit 0 diff --git a/test/0-init-fail.sh b/test/0-init-fail.sh index db594b2..c440a57 100755 --- a/test/0-init-fail.sh +++ b/test/0-init-fail.sh @@ -1,9 +1,12 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-fail-pack" deps="logger PACKAGE_DONT_EXIST" # would be very unlucky if it does exist... -$DUB init -n $packname $deps +if $DUB init -n $packname $deps 2>/dev/null; then + die $LINENO 'Init with unknown non-existing dependency expected to fail' +fi function cleanup { rm -rf $packname @@ -11,6 +14,5 @@ if [ -e $packname/dub.sdl ]; then # package is there, it should have failed cleanup - exit 1 + die $LINENO "$packname/dub.sdl was not created" fi -exit 0 diff --git a/test/0-init-interactive.sh b/test/0-init-interactive.sh index 65065f3..945838b 100755 --- a/test/0-init-interactive.sh +++ b/test/0-init-interactive.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-interactive" echo -e "sdl\ntest\ndesc\nauthor\ngpl\ncopy\n\n" | $DUB init $packname @@ -9,16 +10,13 @@ } if [ ! -e $packname/dub.sdl ]; then # it failed - echo "No dub.sdl file has been generated." cleanup - exit 1 + die $LINENO 'No dub.sdl file has been generated.' fi if ! diff $packname/dub.sdl "$CURR_DIR"/0-init-interactive.dub.sdl; then - echo "Contents of generated dub.sdl not as expected." - cleanup - exit 1 + cleanup + die $LINENO 'Contents of generated dub.sdl not as expected.' fi cleanup -exit 0 diff --git a/test/0-init-multi-json.sh b/test/0-init-multi-json.sh index 8e0a441..239c419 100755 --- a/test/0-init-multi-json.sh +++ b/test/0-init-multi-json.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-multi-pack" deps="openssl logger" type="vibe.d" @@ -10,20 +11,17 @@ rm -rf $packname } -if [ ! -e $packname/dub.json ]; then # it failed, exit 1 - exit 1 -else # check if resulting dub.json has all dependancies in tow +if [ ! -e $packname/dub.json ]; then + die $LINENO '$packname/dub.json not created' +else # check if resulting dub.json has all dependencies in tow deps="$deps vibe-d"; IFS=" " read -a arr <<< "$deps" for ele in "${arr[@]}" do if [ `grep -c "$ele" $packname/dub.json` -ne 1 ]; then #something went wrong - echo "$ele not in $packname/dub.json" cleanup - exit 1 + die $LINENO "$ele not in $packname/dub.json" fi done cleanup - exit 0 - fi diff --git a/test/0-init-multi.sh b/test/0-init-multi.sh index 5f74a3d..8432b96 100755 --- a/test/0-init-multi.sh +++ b/test/0-init-multi.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-multi-pack" deps="openssl logger" type="vibe.d" @@ -10,22 +11,18 @@ rm -rf $packname } -if [ ! -e $packname/dub.sdl ]; then # it failed, exit 1 - echo "No dub.sdl file has been generated." +if [ ! -e $packname/dub.sdl ]; then cleanup - exit 1 -else # check if resulting dub.sdl has all dependancies in tow + die $LINENO 'No dub.sdl file has been generated.' +else # check if resulting dub.sdl has all dependencies in tow deps="$deps vibe-d"; IFS=" " read -a arr <<< "$deps" for ele in "${arr[@]}" do if [ `grep -c "$ele" $packname/dub.sdl` -ne 1 ]; then #something went wrong - echo "$ele not in $packname/dub.sdl" cleanup - exit 1 + die $LINENO "$ele not in $packname/dub.sdl" fi done cleanup - exit 0 - fi diff --git a/test/0-init-simple-json.sh b/test/0-init-simple-json.sh index a18bd3d..2a7ec8a 100755 --- a/test/0-init-simple-json.sh +++ b/test/0-init-simple-json.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-simple-pack" $DUB init -n $packname -f json @@ -8,9 +9,8 @@ rm -rf $packname } -if [ ! -e $packname/dub.json ]; then # it failed +if [ ! -e $packname/dub.json ]; then cleanup - exit 1 + die $LINENO 'No dub.json file has been generated.' fi cleanup -exit 0 diff --git a/test/0-init-simple.sh b/test/0-init-simple.sh index 6b25f5a..f4fee2e 100755 --- a/test/0-init-simple.sh +++ b/test/0-init-simple.sh @@ -1,5 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh packname="0-init-simple-pack" $DUB init -n $packname --format sdl @@ -9,9 +10,7 @@ } if [ ! -e $packname/dub.sdl ]; then # it failed - echo "No dub.sdl file has been generated." cleanup - exit 1 + die $LINENO 'No dub.sdl file has been generated.' fi cleanup -exit 0 diff --git a/test/1-dynLib-simple/.no_build b/test/1-dynLib-simple/.no_build deleted file mode 100644 index 72679d2..0000000 --- a/test/1-dynLib-simple/.no_build +++ /dev/null @@ -1 +0,0 @@ -Remove me when bug with dynamic libs get fixed. diff --git a/test/1-dynLib-simple/.no_build_gdc b/test/1-dynLib-simple/.no_build_gdc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/1-dynLib-simple/.no_build_gdc diff --git a/test/1-dynLib-simple/.no_build_ldc2 b/test/1-dynLib-simple/.no_build_ldc2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/1-dynLib-simple/.no_build_ldc2 diff --git a/test/1-dynLib-simple/.no_run b/test/1-dynLib-simple/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/1-dynLib-simple/.no_run diff --git a/test/2-dynLib-with-staticLib-dep/.no_build_gdc b/test/2-dynLib-with-staticLib-dep/.no_build_gdc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/2-dynLib-with-staticLib-dep/.no_build_gdc diff --git a/test/2-dynLib-with-staticLib-dep/.no_build_ldc2 b/test/2-dynLib-with-staticLib-dep/.no_build_ldc2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/2-dynLib-with-staticLib-dep/.no_build_ldc2 diff --git a/test/2-dynLib-with-staticLib-dep/.no_run b/test/2-dynLib-with-staticLib-dep/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/2-dynLib-with-staticLib-dep/.no_run diff --git a/test/2-dynLib-with-staticLib-dep/dub.json b/test/2-dynLib-with-staticLib-dep/dub.json new file mode 100644 index 0000000..f0b6043 --- /dev/null +++ b/test/2-dynLib-with-staticLib-dep/dub.json @@ -0,0 +1,7 @@ +{ + "name": "dynlib-with-staticlib-dep", + "targetType": "dynamicLibrary", + "dependencies": { + "staticlib-simple": { "path": "../1-staticLib-simple/" } + } +} diff --git a/test/2-dynLib-with-staticLib-dep/source/dynlib/app.d b/test/2-dynLib-with-staticLib-dep/source/dynlib/app.d new file mode 100644 index 0000000..9741cae --- /dev/null +++ b/test/2-dynLib-with-staticLib-dep/source/dynlib/app.d @@ -0,0 +1,8 @@ +module dynlib.app; +import std.stdio; +import staticlib.app; + +void foo() +{ + entry(); +} diff --git a/test/4-describe-data-1-list.sh b/test/4-describe-data-1-list.sh index aefbf78..630b609 100755 --- a/test/4-describe-data-1-list.sh +++ b/test/4-describe-data-1-list.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/describe-project @@ -12,7 +12,8 @@ trap cleanup EXIT -if ! $DUB describe --compiler=$DC --data-list \ +if ! $DUB describe --compiler=$DC --filter-versions \ + --data-list \ '--data= target-type , target-path , target-name ' \ '--data= working-directory ' \ --data=main-source-file \ @@ -30,7 +31,7 @@ --data=post-build-commands \ '--data=requirements, options' \ > "$temp_file"; then - die 'Printing project data failed!' + die $LINENO 'Printing project data failed!' fi # Create the expected output path file to compare against. @@ -78,9 +79,6 @@ # --data=versions echo "someVerIdent" >> "$expected_file" echo "anotherVerIdent" >> "$expected_file" -echo "Have_describe_project" >> "$expected_file" -echo "Have_describe_dependency_1" >> "$expected_file" -echo "Have_describe_dependency_2" >> "$expected_file" echo "Have_describe_dependency_3" >> "$expected_file" echo >> "$expected_file" # --data=debug-versions @@ -134,6 +132,6 @@ #echo "stackStomping" >> "$expected_file" # Not sure if this (from a sourceLib dependency) should be missing from the result if ! diff "$expected_file" "$temp_file"; then - die 'The project data did not match the expected output!' + die $LINENO 'The project data did not match the expected output!' fi diff --git a/test/4-describe-data-2-dmd.sh b/test/4-describe-data-2-dmd.sh index 3f30769..8d7d0ae 100755 --- a/test/4-describe-data-2-dmd.sh +++ b/test/4-describe-data-2-dmd.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh if [ "${DC}" != "dmd" ]; then echo Skipping DMD-centric test on configuration that lacks DMD. @@ -17,7 +17,7 @@ trap cleanup EXIT -if ! $DUB describe --compiler=${DC} \ +if ! $DUB describe --compiler=$DC --filter-versions \ --data=main-source-file \ --data=dflags,lflags \ --data=libs,linker-files \ @@ -55,9 +55,6 @@ # --data=versions echo -n "-version=someVerIdent " >> "$expected_file" echo -n "-version=anotherVerIdent " >> "$expected_file" -echo -n "-version=Have_describe_project " >> "$expected_file" -echo -n "-version=Have_describe_dependency_1 " >> "$expected_file" -echo -n "-version=Have_describe_dependency_2 " >> "$expected_file" echo -n "-version=Have_describe_dependency_3 " >> "$expected_file" # --data=debug-versions echo -n "-debug=someDebugVerIdent " >> "$expected_file" diff --git a/test/4-describe-data-3-zero-delim.sh b/test/4-describe-data-3-zero-delim.sh index 2d6738b..aee4d08 100755 --- a/test/4-describe-data-3-zero-delim.sh +++ b/test/4-describe-data-3-zero-delim.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/describe-project diff --git a/test/4-describe-import-paths.sh b/test/4-describe-import-paths.sh index ba03205..375bc40 100755 --- a/test/4-describe-import-paths.sh +++ b/test/4-describe-import-paths.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/describe-project diff --git a/test/4-describe-json.sh b/test/4-describe-json.sh index 5641595..21f5105 100755 --- a/test/4-describe-json.sh +++ b/test/4-describe-json.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/describe-project diff --git a/test/4-describe-string-import-paths.sh b/test/4-describe-string-import-paths.sh index aa64979..c1106a6 100755 --- a/test/4-describe-string-import-paths.sh +++ b/test/4-describe-string-import-paths.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/describe-project diff --git a/test/5-convert-stdout.sh b/test/5-convert-stdout.sh index 86cba02..ae7b491 100755 --- a/test/5-convert-stdout.sh +++ b/test/5-convert-stdout.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/usr/bin/env bash -set -e +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/1-exec-simple @@ -10,16 +10,13 @@ RESULT=`${DUB} convert -s -f sdl` if [ ! -f dub.json ]; then - echo "Package recipe got modified!" - exit 1 + die $LINENO 'Package recipe got modified!' fi if [ -f dub.sdl ]; then - echo "An SDL recipe got written." - exit 2 + die $LINENO 'An SDL recipe got written.' fi if [ "$RESULT" != "$EXPECTED" ]; then - echo "Unexpected SDLang output." - exit 3 + die $LINENO 'Unexpected SDLang output.' fi diff --git a/test/5-convert.sh b/test/5-convert.sh index fac1360..5cdbaa9 100755 --- a/test/5-convert.sh +++ b/test/5-convert.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/5-convert @@ -9,31 +9,23 @@ function cleanup { rm $temp_file } - -function die { - echo "$@" 1>&2 - exit 1 -} - trap cleanup EXIT cp dub.sdl dub.sdl.ref $DUB convert -f json -if [ -f "dub.sdl" ]; then die 'Old recipe file not removed.'; fi -if [ ! -f "dub.json" ]; then die 'New recipe file not created.'; fi +if [ -f "dub.sdl" ]; then die $LINENO 'Old recipe file not removed.'; fi +if [ ! -f "dub.json" ]; then die $LINENO 'New recipe file not created.'; fi $DUB convert -f sdl -if [ -f "dub.json" ]; then die 'Old recipe file not removed.'; fi -if [ ! -f "dub.sdl" ]; then die 'New recipe file not created.'; fi +if [ -f "dub.json" ]; then die $LINENO 'Old recipe file not removed.'; fi +if [ ! -f "dub.sdl" ]; then die $LINENO 'New recipe file not created.'; fi if ! diff "dub.sdl" "dub.sdl.ref"; then - die 'The project data did not match the expected output!' + die $LINENO 'The project data did not match the expected output!' fi rm dub.sdl.ref -echo OK - diff --git a/test/common.sh b/test/common.sh new file mode 100644 index 0000000..eba424d --- /dev/null +++ b/test/common.sh @@ -0,0 +1,21 @@ +SOURCE_FILE=$_ + +set -ueEo pipefail + +function log() { + echo -e "\033[0;33m[INFO] $@\033[0m" + echo "[INFO] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log +} + +# lineno[, msg] +function die() { + local line=$1 + local msg=${2:-command failed} + local supplemental=${3:-} + echo "[ERROR] $SOURCE_FILE:$1 $msg" | tee -a $(dirname "${BASH_SOURCE[0]}")/test.log | cat 1>&2 + if [ ! -z "$supplemental" ]; then + echo "$supplemental" | >&2 sed 's|^| |g' + fi + exit 1 +} +trap 'die $LINENO' ERR diff --git a/test/ddox.sh b/test/ddox.sh index c68e023..9675ed8 100755 --- a/test/ddox.sh +++ b/test/ddox.sh @@ -1,6 +1,12 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +# gdc 4.8.5 not working with ddox due to missing +# std.experimental.allocator.mallocator for libdparse +if [ ${DC} = gdc ]; then + exit 0 +fi (cd $CURR_DIR/ddox/default && $DUB build -b ddox) grep -qF ddox_project $CURR_DIR/ddox/default/docs/index.html diff --git a/test/ddox.sh.min_frontend b/test/ddox.sh.min_frontend index 340aa8d..0443fae 100644 --- a/test/ddox.sh.min_frontend +++ b/test/ddox.sh.min_frontend @@ -1 +1 @@ -2.069 +2.072 diff --git a/test/describe-dependency-1/dependency-postGenerateCommands.sh b/test/describe-dependency-1/dependency-postGenerateCommands.sh index 1a24852..f1f641a 100755 --- a/test/describe-dependency-1/dependency-postGenerateCommands.sh +++ b/test/describe-dependency-1/dependency-postGenerateCommands.sh @@ -1 +1 @@ -#!/bin/sh +#!/usr/bin/env bash diff --git a/test/describe-dependency-1/dependency-preGenerateCommands.sh b/test/describe-dependency-1/dependency-preGenerateCommands.sh index 1a24852..f1f641a 100755 --- a/test/describe-dependency-1/dependency-preGenerateCommands.sh +++ b/test/describe-dependency-1/dependency-preGenerateCommands.sh @@ -1 +1 @@ -#!/bin/sh +#!/usr/bin/env bash diff --git a/test/describe-dependency-1/otherdir/dummy.d b/test/describe-dependency-1/otherdir/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-dependency-1/otherdir/dummy.d +++ b/test/describe-dependency-1/otherdir/dummy.d @@ -1 +1 @@ - + diff --git a/test/describe-dependency-1/source/dummy.d b/test/describe-dependency-1/source/dummy.d index 8d1c8b6..3c1514d 100644 --- a/test/describe-dependency-1/source/dummy.d +++ b/test/describe-dependency-1/source/dummy.d @@ -1 +1,2 @@ - +version (anotherVerIdent) {} +debug (anotherDebugVerIdent) {} diff --git a/test/describe-dependency-2/some-extra-string-import-path/dummy.d b/test/describe-dependency-2/some-extra-string-import-path/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-dependency-2/some-extra-string-import-path/dummy.d +++ b/test/describe-dependency-2/some-extra-string-import-path/dummy.d @@ -1 +1 @@ - + diff --git a/test/describe-dependency-2/some-path/dummy.d b/test/describe-dependency-2/some-path/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-dependency-2/some-path/dummy.d +++ b/test/describe-dependency-2/some-path/dummy.d @@ -1 +1 @@ - + diff --git a/test/describe-dependency-3/dep3-source/dummy.d b/test/describe-dependency-3/dep3-source/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-dependency-3/dep3-source/dummy.d +++ b/test/describe-dependency-3/dep3-source/dummy.d @@ -1 +1 @@ - + diff --git a/test/describe-dependency-3/dep3-string-import-path/dummy.d b/test/describe-dependency-3/dep3-string-import-path/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-dependency-3/dep3-string-import-path/dummy.d +++ b/test/describe-dependency-3/dep3-string-import-path/dummy.d @@ -1 +1 @@ - + diff --git a/test/describe-project/do-postGenerateCommands.sh b/test/describe-project/do-postGenerateCommands.sh index 1a24852..f1f641a 100755 --- a/test/describe-project/do-postGenerateCommands.sh +++ b/test/describe-project/do-postGenerateCommands.sh @@ -1 +1 @@ -#!/bin/sh +#!/usr/bin/env bash diff --git a/test/describe-project/do-preGenerateCommands.sh b/test/describe-project/do-preGenerateCommands.sh index 1a24852..f1f641a 100755 --- a/test/describe-project/do-preGenerateCommands.sh +++ b/test/describe-project/do-preGenerateCommands.sh @@ -1 +1 @@ -#!/bin/sh +#!/usr/bin/env bash diff --git a/test/describe-project/src/dummy.d b/test/describe-project/src/dummy.d index 8d1c8b6..733017c 100644 --- a/test/describe-project/src/dummy.d +++ b/test/describe-project/src/dummy.d @@ -1 +1,3 @@ - +version (Have_describe_dependency_3) {} +version (someVerIdent) {} +debug (someDebugVerIdent) {} diff --git a/test/describe-project/views/dummy.d b/test/describe-project/views/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/describe-project/views/dummy.d +++ b/test/describe-project/views/dummy.d @@ -1 +1 @@ - + diff --git a/test/expected-issue1037-output b/test/expected-issue1037-output new file mode 100644 index 0000000..e03b110 --- /dev/null +++ b/test/expected-issue1037-output @@ -0,0 +1,3 @@ +Unresolvable dependencies to package gitcompatibledubpackage: + b >=0.0.0 @DIR/b depends on gitcompatibledubpackage ~>1.0.2 + issue1037-better-dependency-messages ~master depends on gitcompatibledubpackage 1.0.1 diff --git a/test/feat663-search.sh b/test/feat663-search.sh index 1f6f3a4..21cea3e 100755 --- a/test/feat663-search.sh +++ b/test/feat663-search.sh @@ -1,5 +1,15 @@ -#!/bin/bash +#!/usr/bin/env bash -${DUB} search 2>/dev/null && exit 1 -${DUB} search nonexistent123456789package 2>/dev/null && exit 1 -${DUB} search dub | grep -q '^dub' || exit 1 +. $(dirname "${BASH_SOURCE[0]}")/common.sh +if ${DUB} search 2>/dev/null; then + die $LINENO '`dub search` succeeded' +fi +if ${DUB} search nonexistent123456789package 2>/dev/null; then + die $LINENO '`dub search nonexistent123456789package` succeeded' +fi +if ! OUTPUT=$(${DUB} search '"dub-registry"' -v 2>&1); then + die $LINENO '`dub search "dub-registry"` failed' "$OUTPUT" +fi +if ! grep -q '^dub-registry (.*)\s'<<<"$OUTPUT"; then + die $LINENO '`grep -q '"'"'^dub-registry (.*)\s'"'"'` failed' "$OUTPUT" +fi diff --git a/test/fetchzip.sh b/test/fetchzip.sh new file mode 100755 index 0000000..723e698 --- /dev/null +++ b/test/fetchzip.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +dub remove gitcompatibledubpackage --non-interactive --version=* 2>/dev/null || true + +"$DUB" build --single "$DIR"/test_registry.d +"$DIR"/test_registry --folder="$DIR/issue1336-registry" --port=$PORT & +PID=$! +sleep 1 +trap 'kill $PID 2>/dev/null || true' exit + +echo "Trying to download gitcompatibledubpackage (1.0.4)" +timeout 1s "$DUB" fetch gitcompatibledubpackage --version=1.0.4 --skip-registry=all --registry=http://localhost:$PORT +if [ $? -eq 124 ]; then + die 'Fetching from responsive registry should not time-out.' +fi +dub remove gitcompatibledubpackage --non-interactive --version=1.0.4 + +echo "Downloads should be retried when the zip is corrupted - gitcompatibledubpackage (1.0.3)" +zipOut=$(! timeout 1s "$DUB" fetch gitcompatibledubpackage --version=1.0.3 --skip-registry=all --registry=http://localhost:$PORT 2>&1) +rc=$? + +if ! zipCount=$(grep -Fc 'Failed to extract zip archive' <<<"$zipOut") || [ "$zipCount" -lt 3 ] ; then + echo '========== +Output was ==========' >&2 + echo "$zipOut" >&2 + echo '========== -Output was ==========' >&2 + die 'DUB should have tried to download the zip archive multiple times.' +elif [ $rc -eq 124 ]; then + die 'DUB timed out unexpectedly.' +fi +if dub remove gitcompatibledubpackage --non-interactive --version=* 2>/dev/null; then + die 'DUB should not have installed a broken package.' +fi + +echo "HTTP status errors on downloads should be retried - gitcompatibledubpackage (1.0.2)" +retryOut=$(! timeout 1s "$DUB" fetch gitcompatibledubpackage --version=1.0.2 --skip-registry=all --registry=http://localhost:$PORT --vverbose 2>&1) +rc=$? +if ! retryCount=$(echo "$retryOut" | grep -Fc 'Bad Gateway') || [ "$retryCount" -lt 3 ] ; then + echo '========== +Output was ==========' >&2 + echo "$retryOut" >&2 + echo '========== -Output was ==========' >&2 + die "DUB should have retried download on server error multiple times, but only tried $retryCount times." +elif [ $rc -eq 124 ]; then + die 'DUB timed out unexpectedly.' +fi +if dub remove gitcompatibledubpackage --non-interactive --version=* 2>/dev/null; then + die 'DUB should not have installed a package.' +fi + +echo "HTTP status errors on downloads should retry with fallback mirror - gitcompatibledubpackage (1.0.2)" +timeout 1s "$DUB" fetch gitcompatibledubpackage --version=1.0.2 --skip-registry=all --registry="http://localhost:$PORT http://localhost:$PORT/fallback" +if [ $? -eq 124 ]; then + die 'Fetching from responsive registry should not time-out.' +fi +dub remove gitcompatibledubpackage --non-interactive --version=1.0.2 diff --git a/test/fetchzip.sh.min_frontend b/test/fetchzip.sh.min_frontend new file mode 100644 index 0000000..bb0a2e1 --- /dev/null +++ b/test/fetchzip.sh.min_frontend @@ -0,0 +1 @@ +2.076 diff --git a/test/interactive-remove.sh b/test/interactive-remove.sh index 5c23bef..d00a41c 100755 --- a/test/interactive-remove.sh +++ b/test/interactive-remove.sh @@ -1,36 +1,31 @@ -#!/bin/bash +#!/usr/bin/env bash -set -euo pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh -$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] -$DUB fetch dub --version=0.9.21 && [ -d $HOME/.dub/packages/dub-0.9.21/dub ] -if $DUB remove dub --non-interactive; then - echo "Non-interactive remove should fail" 1>&2 - exit 1 +$DUB fetch dub --version=1.9.0 && [ -d $HOME/.dub/packages/dub-1.9.0/dub ] +$DUB fetch dub --version=1.10.0 && [ -d $HOME/.dub/packages/dub-1.10.0/dub ] +if $DUB remove dub --non-interactive 2>/dev/null; then + die $LINENO 'Non-interactive remove should fail' fi -echo 1 | $DUB remove dub | tr --delete '\n' | grep --ignore-case 'select.*0\.9\.20.*0\.9\.21.*' -if [ -d $HOME/.dub/packages/dub-0.9.20/dub ]; then - echo "Failed to remove dub-0.9.20" 1>&2 - exit 1 +echo 1 | $DUB remove dub | tr -d '\n' | grep --ignore-case 'select.*1\.9\.0.*1\.10\.0.*' +if [ -d $HOME/.dub/packages/dub-1.9.0/dub ]; then + die $LINENO 'Failed to remove dub-1.9.0' fi -$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] +$DUB fetch dub --version=1.9.0 && [ -d $HOME/.dub/packages/dub-1.9.0/dub ] # EOF aborts remove echo -xn '' | $DUB remove dub -if [ ! -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ ! -d $HOME/.dub/packages/dub-0.9.21/dub ]; then - echo "Aborted dub still removed a package" 1>&2 - exit 1 +if [ ! -d $HOME/.dub/packages/dub-1.9.0/dub ] || [ ! -d $HOME/.dub/packages/dub-1.10.0/dub ]; then + die $LINENO 'Aborted dub still removed a package' fi # validates input echo -e 'abc\n4\n-1\n3' | $DUB remove dub -if [ -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ -d $HOME/.dub/packages/dub-0.9.21/dub ]; then - echo "Failed to remove all version of dub" 1>&2 - exit 1 +if [ -d $HOME/.dub/packages/dub-1.9.0/dub ] || [ -d $HOME/.dub/packages/dub-1.10.0/dub ]; then + die $LINENO 'Failed to remove all version of dub' fi -$DUB fetch dub --version=0.9.20 && [ -d $HOME/.dub/packages/dub-0.9.20/dub ] -$DUB fetch dub --version=0.9.21 && [ -d $HOME/.dub/packages/dub-0.9.21/dub ] +$DUB fetch dub --version=1.9.0 && [ -d $HOME/.dub/packages/dub-1.9.0/dub ] +$DUB fetch dub@1.10.0 && [ -d $HOME/.dub/packages/dub-1.10.0/dub ] # is non-interactive with --version= $DUB remove dub --version=\* -if [ -d $HOME/.dub/packages/dub-0.9.20/dub ] || [ -d $HOME/.dub/packages/dub-0.9.21/dub ]; then - echo 'Failed to non-interactively remove specified versions' 1>&2 - exit 1 +if [ -d $HOME/.dub/packages/dub-1.9.0/dub ] || [ -d $HOME/.dub/packages/dub-1.10.0/dub ]; then + die $LINENO 'Failed to non-interactively remove specified versions' fi diff --git a/test/issue1003-check-empty-ld-flags.sh b/test/issue1003-check-empty-ld-flags.sh new file mode 100755 index 0000000..61678f7 --- /dev/null +++ b/test/issue1003-check-empty-ld-flags.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cd ${CURR_DIR}/issue1003-check-empty-ld-flags + +${DUB} build --compiler=${DC} --force diff --git a/test/issue1003-check-empty-ld-flags/dub.json b/test/issue1003-check-empty-ld-flags/dub.json new file mode 100644 index 0000000..7c4c251 --- /dev/null +++ b/test/issue1003-check-empty-ld-flags/dub.json @@ -0,0 +1,10 @@ +{ + "authors": [ + "--" + ], + "copyright": "Copyright © 2019, --", + "description": "--", + "license": "--", + "name": "issue1003-empty-ld-flags", + "lflags": [""] +} diff --git a/test/issue1003-check-empty-ld-flags/source/app.d b/test/issue1003-check-empty-ld-flags/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue1003-check-empty-ld-flags/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue1004-override-config.sh b/test/issue1004-override-config.sh new file mode 100755 index 0000000..96080f1 --- /dev/null +++ b/test/issue1004-override-config.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1004-override-config +${DUB} build --bare main --override-config a/success diff --git a/test/issue1004-override-config/.no_build b/test/issue1004-override-config/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1004-override-config/.no_build diff --git a/test/issue1004-override-config/a/a.d b/test/issue1004-override-config/a/a.d new file mode 100644 index 0000000..af17f99 --- /dev/null +++ b/test/issue1004-override-config/a/a.d @@ -0,0 +1,5 @@ +module a; + +void test() +{ +} diff --git a/test/issue1004-override-config/a/dub.sdl b/test/issue1004-override-config/a/dub.sdl new file mode 100644 index 0000000..9c01efd --- /dev/null +++ b/test/issue1004-override-config/a/dub.sdl @@ -0,0 +1,9 @@ +name "a" + +configuration "fail" { +} + +configuration "success" { + sourceFiles "a.d" + importPaths "." +} diff --git a/test/issue1004-override-config/main/dub.sdl b/test/issue1004-override-config/main/dub.sdl new file mode 100644 index 0000000..b2287f0 --- /dev/null +++ b/test/issue1004-override-config/main/dub.sdl @@ -0,0 +1,2 @@ +name "main" +dependency "a" version="*" diff --git a/test/issue1004-override-config/main/source/main.d b/test/issue1004-override-config/main/source/main.d new file mode 100644 index 0000000..b248b89 --- /dev/null +++ b/test/issue1004-override-config/main/source/main.d @@ -0,0 +1,6 @@ +import a; + +void main() +{ + test(); +} diff --git a/test/issue1005-configuration-resolution.sh b/test/issue1005-configuration-resolution.sh new file mode 100755 index 0000000..1233e76 --- /dev/null +++ b/test/issue1005-configuration-resolution.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1005-configuration-resolution +${DUB} build --bare main diff --git a/test/issue1005-configuration-resolution/.no_build b/test/issue1005-configuration-resolution/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1005-configuration-resolution/.no_build diff --git a/test/issue1005-configuration-resolution/a/dub.sdl b/test/issue1005-configuration-resolution/a/dub.sdl new file mode 100644 index 0000000..d19952b --- /dev/null +++ b/test/issue1005-configuration-resolution/a/dub.sdl @@ -0,0 +1,10 @@ +name "a" +dependency "b" version="*" + +configuration "x" { + subConfiguration "b" "x" +} + +configuration "y" { + subConfiguration "b" "y" +} diff --git a/test/issue1005-configuration-resolution/b/dub.sdl b/test/issue1005-configuration-resolution/b/dub.sdl new file mode 100644 index 0000000..3cfa48b --- /dev/null +++ b/test/issue1005-configuration-resolution/b/dub.sdl @@ -0,0 +1,7 @@ +name "b" + +configuration "x" { +} + +configuration "y" { +} \ No newline at end of file diff --git a/test/issue1005-configuration-resolution/b/source/b.d b/test/issue1005-configuration-resolution/b/source/b.d new file mode 100644 index 0000000..2a9bb41 --- /dev/null +++ b/test/issue1005-configuration-resolution/b/source/b.d @@ -0,0 +1,3 @@ +module b; + +void foo() {} diff --git a/test/issue1005-configuration-resolution/c/dub.sdl b/test/issue1005-configuration-resolution/c/dub.sdl new file mode 100644 index 0000000..e46b148 --- /dev/null +++ b/test/issue1005-configuration-resolution/c/dub.sdl @@ -0,0 +1,2 @@ +name "c" +dependency "a" version="*" diff --git a/test/issue1005-configuration-resolution/main/dub.sdl b/test/issue1005-configuration-resolution/main/dub.sdl new file mode 100644 index 0000000..d492491 --- /dev/null +++ b/test/issue1005-configuration-resolution/main/dub.sdl @@ -0,0 +1,6 @@ +name "main" + +dependency "b" version="*" +dependency "c" version="*" + +subConfiguration "b" "y" diff --git a/test/issue1005-configuration-resolution/main/source/app.d b/test/issue1005-configuration-resolution/main/source/app.d new file mode 100644 index 0000000..0ec7361 --- /dev/null +++ b/test/issue1005-configuration-resolution/main/source/app.d @@ -0,0 +1,6 @@ +import b; + +void main() +{ + foo(); +} diff --git a/test/issue1024-selective-upgrade.sh b/test/issue1024-selective-upgrade.sh new file mode 100755 index 0000000..dc7c009 --- /dev/null +++ b/test/issue1024-selective-upgrade.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1024-selective-upgrade +echo "{\"fileVersion\": 1,\"versions\": {\"a\": \"1.0.0\", \"b\": \"1.0.0\"}}" > main/dub.selections.json +$DUB upgrade --bare --root=main a + +if ! grep -c -e "\"a\": \"1.0.1\"" main/dub.selections.json; then + die $LINENO "Specified dependency was not upgraded." +fi + +if grep -c -e "\"b\": \"1.0.1\"" main/dub.selections.json; then + die $LINENO "Non-specified dependency got upgraded." +fi diff --git a/test/issue1024-selective-upgrade/.no_build b/test/issue1024-selective-upgrade/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1024-selective-upgrade/.no_build diff --git a/test/issue1024-selective-upgrade/a-1.0.0/dub.sdl b/test/issue1024-selective-upgrade/a-1.0.0/dub.sdl new file mode 100644 index 0000000..7ff9fa1 --- /dev/null +++ b/test/issue1024-selective-upgrade/a-1.0.0/dub.sdl @@ -0,0 +1,2 @@ +name "a" +version "1.0.0" diff --git a/test/issue1024-selective-upgrade/a-1.0.1/dub.sdl b/test/issue1024-selective-upgrade/a-1.0.1/dub.sdl new file mode 100644 index 0000000..5c8a407 --- /dev/null +++ b/test/issue1024-selective-upgrade/a-1.0.1/dub.sdl @@ -0,0 +1,2 @@ +name "a" +version "1.0.1" diff --git a/test/issue1024-selective-upgrade/b-1.0.0/dub.sdl b/test/issue1024-selective-upgrade/b-1.0.0/dub.sdl new file mode 100644 index 0000000..5597559 --- /dev/null +++ b/test/issue1024-selective-upgrade/b-1.0.0/dub.sdl @@ -0,0 +1,2 @@ +name "b" +version "1.0.0" diff --git a/test/issue1024-selective-upgrade/b-1.0.1/dub.sdl b/test/issue1024-selective-upgrade/b-1.0.1/dub.sdl new file mode 100644 index 0000000..5e0c01a --- /dev/null +++ b/test/issue1024-selective-upgrade/b-1.0.1/dub.sdl @@ -0,0 +1,2 @@ +name "b" +version "1.0.1" diff --git a/test/issue1024-selective-upgrade/main/dub.sdl b/test/issue1024-selective-upgrade/main/dub.sdl new file mode 100644 index 0000000..a9da177 --- /dev/null +++ b/test/issue1024-selective-upgrade/main/dub.sdl @@ -0,0 +1,3 @@ +name "test" +dependency "a" version="~>1.0.0" +dependency "b" version="~>1.0.0" diff --git a/test/issue103-single-file-package-no-ext b/test/issue103-single-file-package-no-ext new file mode 100755 index 0000000..8c76638 --- /dev/null +++ b/test/issue103-single-file-package-no-ext @@ -0,0 +1,12 @@ +#!../bin/dub +/+ dub.sdl: + name "single-file-test" ++/ +module hello; + +void main(string[] args) +{ + import std.stdio : writeln; + assert(args.length == 4 && args[1 .. 4] == ["foo", "--", "bar"]); + writeln("Hello, World!"); +} diff --git a/test/issue103-single-file-package.sh b/test/issue103-single-file-package.sh index 64c5848..cce4674 100755 --- a/test/issue103-single-file-package.sh +++ b/test/issue103-single-file-package.sh @@ -1,20 +1,25 @@ -#!/bin/sh -set -e +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR} rm -f single-file-test ${DUB} run --single issue103-single-file-package-json.d --compiler=${DC} if [ ! -f single-file-test ]; then - echo "Normal invocation did not produce a binary in the current directory" - exit 1 + die $LINENO 'Normal invocation did not produce a binary in the current directory' fi rm single-file-test ./issue103-single-file-package.d foo -- bar +${DUB} ./issue103-single-file-package foo -- bar +./issue103-single-file-package-no-ext foo -- bar ${DUB} issue103-single-file-package-w-dep.d if [ -f single-file-test ]; then - echo "Shebang invocation produced binary in current directory" + die $LINENO 'Shebang invocation produced binary in current directory' +fi + +if ${DUB} "issue103-single-file-package-error.d" 2> /dev/null; then + echo "Invalid package comment syntax did not trigger an error." exit 1 fi diff --git a/test/issue1037-better-dependency-messages.sh b/test/issue1037-better-dependency-messages.sh new file mode 100755 index 0000000..4c5c87a --- /dev/null +++ b/test/issue1037-better-dependency-messages.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e -o pipefail + +cd ${CURR_DIR}/issue1037-better-dependency-messages + +temp_file=$(mktemp $(basename $0).XXXXXX) +temp_file2=$(mktemp $(basename $0).XXXXXX) +expected_file="$CURR_DIR/expected-issue1037-output" + +function cleanup { + rm -f $temp_file + rm -f $temp_file2 +} + +trap cleanup EXIT + +sed "s#DIR#$CURR_DIR/issue1037-better-dependency-messages#" "$expected_file" > "$temp_file2" + +$DUB upgrade 2>$temp_file && exit 1 # dub upgrade should fail + +if ! diff "$temp_file2" "$temp_file"; then + die 'output not containing conflict information' +fi + +exit 0 diff --git a/test/issue1037-better-dependency-messages/.no_build b/test/issue1037-better-dependency-messages/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1037-better-dependency-messages/.no_build diff --git a/test/issue1037-better-dependency-messages/.no_run b/test/issue1037-better-dependency-messages/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1037-better-dependency-messages/.no_run diff --git a/test/issue1037-better-dependency-messages/.no_test b/test/issue1037-better-dependency-messages/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1037-better-dependency-messages/.no_test diff --git a/test/issue1037-better-dependency-messages/b/dub.json b/test/issue1037-better-dependency-messages/b/dub.json new file mode 100644 index 0000000..92443ac --- /dev/null +++ b/test/issue1037-better-dependency-messages/b/dub.json @@ -0,0 +1,6 @@ +{ + "name": "b", + "dependencies": { + "gitcompatibledubpackage": "~>1.0.2" + } +} \ No newline at end of file diff --git a/test/issue1037-better-dependency-messages/dub.json b/test/issue1037-better-dependency-messages/dub.json new file mode 100644 index 0000000..abb7d17 --- /dev/null +++ b/test/issue1037-better-dependency-messages/dub.json @@ -0,0 +1,10 @@ +{ + "name": "issue1037-better-dependency-messages", + "dependencies": { + "gitcompatibledubpackage": "1.0.1", + "b": { + "path": "b", + "version": "*" + } + } +} \ No newline at end of file diff --git a/test/issue1070-init-mistakes-dirs-as-files.sh b/test/issue1070-init-mistakes-dirs-as-files.sh new file mode 100755 index 0000000..4cd8e85 --- /dev/null +++ b/test/issue1070-init-mistakes-dirs-as-files.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd ${CURR_DIR}/issue1070-init-mistakes-dirs-as-files + +${DUB} init 2>&1 | grep -c "The target directory already contains a 'source/' directory. Aborting." > /dev/null \ No newline at end of file diff --git a/test/issue1070-init-mistakes-dirs-as-files/.no_build b/test/issue1070-init-mistakes-dirs-as-files/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1070-init-mistakes-dirs-as-files/.no_build diff --git a/test/issue1070-init-mistakes-dirs-as-files/.no_run b/test/issue1070-init-mistakes-dirs-as-files/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1070-init-mistakes-dirs-as-files/.no_run diff --git a/test/issue1070-init-mistakes-dirs-as-files/.no_test b/test/issue1070-init-mistakes-dirs-as-files/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1070-init-mistakes-dirs-as-files/.no_test diff --git a/test/issue1070-init-mistakes-dirs-as-files/source/.empty b/test/issue1070-init-mistakes-dirs-as-files/source/.empty new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1070-init-mistakes-dirs-as-files/source/.empty diff --git a/test/issue1091-bogus-rebuild.sh b/test/issue1091-bogus-rebuild.sh new file mode 100755 index 0000000..ae440eb --- /dev/null +++ b/test/issue1091-bogus-rebuild.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cd ${CURR_DIR}/1-exec-simple +rm -f dub.selections.json +${DUB} build --compiler=${DC} 2>&1 | grep -e 'building configuration' -c +${DUB} build --compiler=${DC} 2>&1 | { ! grep -e 'building configuration' -c; } diff --git a/test/issue1117-extra-dependency-files.sh b/test/issue1117-extra-dependency-files.sh new file mode 100755 index 0000000..1059dbc --- /dev/null +++ b/test/issue1117-extra-dependency-files.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1117-extra-dependency-files + + +if ! { ${DUB} build 2>&1 || true; } | grep -cF 'building configuration'; then + die $LINENO 'Build was not executed.' +fi + +if ! { ${DUB} build 2>&1 || true; } | grep -cF 'is up to date'; then + die $LINENO 'Build was executed.' +fi + +touch ./dependency.txt + +if ! { ${DUB} build 2>&1 || true; } | grep -cF 'building configuration'; then + die $LINENO 'Build was not executed.' +fi diff --git a/test/issue1117-extra-dependency-files/.gitignore b/test/issue1117-extra-dependency-files/.gitignore new file mode 100644 index 0000000..304e955 --- /dev/null +++ b/test/issue1117-extra-dependency-files/.gitignore @@ -0,0 +1,8 @@ +.dub +docs.json +__dummy.html +docs/ +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1117-extra-dependency-files/.no_build b/test/issue1117-extra-dependency-files/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1117-extra-dependency-files/.no_build diff --git a/test/issue1117-extra-dependency-files/dependency.txt b/test/issue1117-extra-dependency-files/dependency.txt new file mode 100644 index 0000000..5ab2f8a --- /dev/null +++ b/test/issue1117-extra-dependency-files/dependency.txt @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/test/issue1117-extra-dependency-files/dub.json b/test/issue1117-extra-dependency-files/dub.json new file mode 100644 index 0000000..00e57a9 --- /dev/null +++ b/test/issue1117-extra-dependency-files/dub.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "extraDependencyFiles": ["dependency.txt"] +} \ No newline at end of file diff --git a/test/issue1117-extra-dependency-files/source/app.d b/test/issue1117-extra-dependency-files/source/app.d new file mode 100644 index 0000000..ef93217 --- /dev/null +++ b/test/issue1117-extra-dependency-files/source/app.d @@ -0,0 +1 @@ +void main(){} \ No newline at end of file diff --git a/test/issue1136-temp-copy-files.sh b/test/issue1136-temp-copy-files.sh new file mode 100755 index 0000000..ab935cb --- /dev/null +++ b/test/issue1136-temp-copy-files.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1136-temp-copy-files + +"$DUB" app.d diff --git a/test/issue1136-temp-copy-files/.no_build b/test/issue1136-temp-copy-files/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1136-temp-copy-files/.no_build diff --git a/test/issue1136-temp-copy-files/app.d b/test/issue1136-temp-copy-files/app.d new file mode 100644 index 0000000..3883984 --- /dev/null +++ b/test/issue1136-temp-copy-files/app.d @@ -0,0 +1,15 @@ +/+ dub.sdl: + +name "app" +dependency "mylib" path="./mylib" ++/ + +import std.exception: enforce; +import std.file: exists, thisExePath; +import std.path: dirName, buildPath; + +void main() +{ + string filePath = buildPath(thisExePath.dirName, "helloworld.txt"); + enforce(filePath.exists); +} \ No newline at end of file diff --git a/test/issue1136-temp-copy-files/mylib/dub.sdl b/test/issue1136-temp-copy-files/mylib/dub.sdl new file mode 100644 index 0000000..8276ed1 --- /dev/null +++ b/test/issue1136-temp-copy-files/mylib/dub.sdl @@ -0,0 +1,3 @@ +name "mylib" +copyFiles "./helloworld.txt" +targetType "none" \ No newline at end of file diff --git a/test/issue1136-temp-copy-files/mylib/helloworld.txt b/test/issue1136-temp-copy-files/mylib/helloworld.txt new file mode 100644 index 0000000..bc7774a --- /dev/null +++ b/test/issue1136-temp-copy-files/mylib/helloworld.txt @@ -0,0 +1 @@ +hello world! \ No newline at end of file diff --git a/test/issue1158-stdin-for-single-files.sh b/test/issue1158-stdin-for-single-files.sh new file mode 100755 index 0000000..1d2baea --- /dev/null +++ b/test/issue1158-stdin-for-single-files.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cd ${CURR_DIR}/issue1158-stdin-for-single-files + +if ! { cat stdin.d | ${DUB} - --value=v 2>&1 || true; } | grep -cF '["--value=v"]'; then + die $LINENO 'Stdin for single files failed.' +fi \ No newline at end of file diff --git a/test/issue1158-stdin-for-single-files/.no_build b/test/issue1158-stdin-for-single-files/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1158-stdin-for-single-files/.no_build diff --git a/test/issue1158-stdin-for-single-files/stdin.d b/test/issue1158-stdin-for-single-files/stdin.d new file mode 100644 index 0000000..120a14a --- /dev/null +++ b/test/issue1158-stdin-for-single-files/stdin.d @@ -0,0 +1,7 @@ +/+ dub.sdl: + name "hello" ++/ +void main(string[] args) { + import std.stdio : writeln; + writeln(args[1..$]); +} \ No newline at end of file diff --git a/test/issue1194-warn-wrong-subconfig.sh b/test/issue1194-warn-wrong-subconfig.sh new file mode 100755 index 0000000..414bfe2 --- /dev/null +++ b/test/issue1194-warn-wrong-subconfig.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +OUTPUT=`${DUB} build --root ${CURR_DIR}/issue1194-warn-wrong-subconfig 2>&1 || true` + +# make sure the proper errors occur in the output +echo $OUTPUT | grep -c "sub configuration directive \"bar\" -> \"baz\" references a package that is not specified as a dependency" > /dev/null +echo $OUTPUT | grep -c "sub configuration directive \"staticlib-simple\" -> \"foo\" references a configuration that does not exist" > /dev/null +! echo $OUTPUT | grep -c "sub configuration directive \"sourcelib-simple\" -> \"library\" references a package that is not specified as a dependency" > /dev/null +! echo $OUTPUT | grep -c "sub configuration directive \"sourcelib-simple\" -> \"library\" references a configuration that does not exist" > /dev/null + +# make sure no bogs warnings are issued for packages with no sub configuration directives +OUTPUT=`${DUB} build --root ${CURR_DIR}/1-exec-simple 2>&1` +! echo $OUTPUT | grep -c "sub configuration directive.*references" > /dev/null diff --git a/test/issue1194-warn-wrong-subconfig/.no_build b/test/issue1194-warn-wrong-subconfig/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1194-warn-wrong-subconfig/.no_build diff --git a/test/issue1194-warn-wrong-subconfig/dub.sdl b/test/issue1194-warn-wrong-subconfig/dub.sdl new file mode 100644 index 0000000..867864d --- /dev/null +++ b/test/issue1194-warn-wrong-subconfig/dub.sdl @@ -0,0 +1,8 @@ +name "test" +dependency "staticlib-simple" path="../1-staticLib-simple" +dependency "sourcelib-simple" path="../1-sourceLib-simple" +targetType "executable" + +subConfiguration "staticlib-simple" "foo" +subConfiguration "bar" "baz" +subConfiguration "sourcelib-simple" "library" diff --git a/test/issue1194-warn-wrong-subconfig/source/app.d b/test/issue1194-warn-wrong-subconfig/source/app.d new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/issue1194-warn-wrong-subconfig/source/app.d @@ -0,0 +1 @@ +void main() {} diff --git a/test/issue1262-version-inheritance-diamond/.gitignore b/test/issue1262-version-inheritance-diamond/.gitignore new file mode 100644 index 0000000..c09a597 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance-diamond +issue1262-version-inheritance-diamond.so +issue1262-version-inheritance-diamond.dylib +issue1262-version-inheritance-diamond.dll +issue1262-version-inheritance-diamond.a +issue1262-version-inheritance-diamond.lib +issue1262-version-inheritance-diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance-diamond/.no_run b/test/issue1262-version-inheritance-diamond/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/.no_run diff --git a/test/issue1262-version-inheritance-diamond/.no_test b/test/issue1262-version-inheritance-diamond/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/.no_test diff --git a/test/issue1262-version-inheritance-diamond/daughter/.gitignore b/test/issue1262-version-inheritance-diamond/daughter/.gitignore new file mode 100644 index 0000000..f190acb --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/daughter/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +daughter.so +daughter.dylib +daughter.dll +daughter.a +daughter.lib +daughter-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance-diamond/daughter/dub.sdl b/test/issue1262-version-inheritance-diamond/daughter/dub.sdl new file mode 100644 index 0000000..3a0eee7 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/daughter/dub.sdl @@ -0,0 +1,3 @@ +name "daughter" +versions "Daughter" +dependency "diamond" path="../diamond" diff --git a/test/issue1262-version-inheritance-diamond/daughter/source/dummy.d b/test/issue1262-version-inheritance-diamond/daughter/source/dummy.d new file mode 100644 index 0000000..89139c3 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/daughter/source/dummy.d @@ -0,0 +1,6 @@ +module daughter.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); +version (Diamond) {} else static assert(0, "Expected Diamond to be set"); diff --git a/test/issue1262-version-inheritance-diamond/diamond/.gitignore b/test/issue1262-version-inheritance-diamond/diamond/.gitignore new file mode 100644 index 0000000..c359a73 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/diamond/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +diamond.so +diamond.dylib +diamond.dll +diamond.a +diamond.lib +diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance-diamond/diamond/dub.sdl b/test/issue1262-version-inheritance-diamond/diamond/dub.sdl new file mode 100644 index 0000000..85cb8a1 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/diamond/dub.sdl @@ -0,0 +1,2 @@ +name "diamond" +versions "Diamond" diff --git a/test/issue1262-version-inheritance-diamond/diamond/source/dummy.d b/test/issue1262-version-inheritance-diamond/diamond/source/dummy.d new file mode 100644 index 0000000..fb1499e --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/diamond/source/dummy.d @@ -0,0 +1,6 @@ +module diamond.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); +version (Diamond) {} else static assert(0, "Expected Diamond to be set"); diff --git a/test/issue1262-version-inheritance-diamond/dub.sdl b/test/issue1262-version-inheritance-diamond/dub.sdl new file mode 100644 index 0000000..588adb8 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/dub.sdl @@ -0,0 +1,4 @@ +name "issue1262-version-inheritance-diamond" +versions "Parent" +dependency "daughter" path="daughter" +dependency "son" path="son" diff --git a/test/issue1262-version-inheritance-diamond/son/.gitignore b/test/issue1262-version-inheritance-diamond/son/.gitignore new file mode 100644 index 0000000..601a33b --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/son/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +son.so +son.dylib +son.dll +son.a +son.lib +son-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance-diamond/son/dub.sdl b/test/issue1262-version-inheritance-diamond/son/dub.sdl new file mode 100644 index 0000000..41dc1be --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/son/dub.sdl @@ -0,0 +1,3 @@ +name "son" +versions "Son" +dependency "diamond" path="../diamond" diff --git a/test/issue1262-version-inheritance-diamond/son/source/dummy.d b/test/issue1262-version-inheritance-diamond/son/source/dummy.d new file mode 100644 index 0000000..85841ad --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/son/source/dummy.d @@ -0,0 +1,6 @@ +module son.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); +version (Diamond) {} else static assert(0, "Expected Diamond to be set"); diff --git a/test/issue1262-version-inheritance-diamond/source/app.d b/test/issue1262-version-inheritance-diamond/source/app.d new file mode 100644 index 0000000..10f0ae6 --- /dev/null +++ b/test/issue1262-version-inheritance-diamond/source/app.d @@ -0,0 +1,8 @@ +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); +version (Diamond) {} else static assert(0, "Expected Diamond to be set"); + +void main() +{ +} diff --git a/test/issue1262-version-inheritance/.gitignore b/test/issue1262-version-inheritance/.gitignore new file mode 100644 index 0000000..84dca77 --- /dev/null +++ b/test/issue1262-version-inheritance/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance +issue1262-version-inheritance.so +issue1262-version-inheritance.dylib +issue1262-version-inheritance.dll +issue1262-version-inheritance.a +issue1262-version-inheritance.lib +issue1262-version-inheritance-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance/.no_run b/test/issue1262-version-inheritance/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1262-version-inheritance/.no_run diff --git a/test/issue1262-version-inheritance/.no_test b/test/issue1262-version-inheritance/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1262-version-inheritance/.no_test diff --git a/test/issue1262-version-inheritance/daughter/.gitignore b/test/issue1262-version-inheritance/daughter/.gitignore new file mode 100644 index 0000000..f190acb --- /dev/null +++ b/test/issue1262-version-inheritance/daughter/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +daughter.so +daughter.dylib +daughter.dll +daughter.a +daughter.lib +daughter-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance/daughter/dub.sdl b/test/issue1262-version-inheritance/daughter/dub.sdl new file mode 100644 index 0000000..baa3295 --- /dev/null +++ b/test/issue1262-version-inheritance/daughter/dub.sdl @@ -0,0 +1,3 @@ +name "daughter" +versions "Daughter" + diff --git a/test/issue1262-version-inheritance/daughter/source/dummy.d b/test/issue1262-version-inheritance/daughter/source/dummy.d new file mode 100644 index 0000000..aad7fc8 --- /dev/null +++ b/test/issue1262-version-inheritance/daughter/source/dummy.d @@ -0,0 +1,5 @@ +module daughter.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) static assert(0, "Expected Son to no be set"); diff --git a/test/issue1262-version-inheritance/dub.sdl b/test/issue1262-version-inheritance/dub.sdl new file mode 100644 index 0000000..46568fb --- /dev/null +++ b/test/issue1262-version-inheritance/dub.sdl @@ -0,0 +1,4 @@ +name "issue1262-version-inheritance" +versions "Parent" +dependency "daughter" path="daughter" +dependency "son" path="son" diff --git a/test/issue1262-version-inheritance/son/.gitignore b/test/issue1262-version-inheritance/son/.gitignore new file mode 100644 index 0000000..601a33b --- /dev/null +++ b/test/issue1262-version-inheritance/son/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +son.so +son.dylib +son.dll +son.a +son.lib +son-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1262-version-inheritance/son/dub.sdl b/test/issue1262-version-inheritance/son/dub.sdl new file mode 100644 index 0000000..5882548 --- /dev/null +++ b/test/issue1262-version-inheritance/son/dub.sdl @@ -0,0 +1,2 @@ +name "son" +versions "Son" diff --git a/test/issue1262-version-inheritance/son/source/dummy.d b/test/issue1262-version-inheritance/son/source/dummy.d new file mode 100644 index 0000000..63995a1 --- /dev/null +++ b/test/issue1262-version-inheritance/son/source/dummy.d @@ -0,0 +1,5 @@ +module son.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) static assert(0, "Expected Daughter to not be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); diff --git a/test/issue1262-version-inheritance/source/app.d b/test/issue1262-version-inheritance/source/app.d new file mode 100644 index 0000000..b6bb4be --- /dev/null +++ b/test/issue1262-version-inheritance/source/app.d @@ -0,0 +1,7 @@ +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); + +void main() +{ +} diff --git a/test/issue1336-registry/.gitignore b/test/issue1336-registry/.gitignore new file mode 100644 index 0000000..86e1bdf --- /dev/null +++ b/test/issue1336-registry/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +issue1336-registry.so +issue1336-registry.dylib +issue1336-registry.dll +issue1336-registry.a +issue1336-registry.lib +issue1336-registry-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1336-registry/.no_build b/test/issue1336-registry/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1336-registry/.no_build diff --git a/test/issue1336-registry/api/packages/infos__packages=%5B%22gitcompatibledubpackage%22%5D&include_dependencies=true&minimize=true b/test/issue1336-registry/api/packages/infos__packages=%5B%22gitcompatibledubpackage%22%5D&include_dependencies=true&minimize=true new file mode 100644 index 0000000..7258e3d --- /dev/null +++ b/test/issue1336-registry/api/packages/infos__packages=%5B%22gitcompatibledubpackage%22%5D&include_dependencies=true&minimize=true @@ -0,0 +1,3 @@ +{ + "gitcompatibledubpackage": {"versions":[{"configurations":[{"name":"exe"},{"name":"lib"}],"version":"~master","name":"gitcompatibledubpackage"},{"version":"1.0.1","name":"gitcompatibledubpackage"},{"version":"1.0.2","name":"gitcompatibledubpackage"},{"version":"1.0.3","name":"gitcompatibledubpackage"},{"configurations":[{"name":"exe"},{"name":"lib"}],"version":"1.0.4","name":"gitcompatibledubpackage"}]} +} \ No newline at end of file diff --git a/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.2.zip b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.2.zip new file mode 120000 index 0000000..cd46966 --- /dev/null +++ b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.2.zip @@ -0,0 +1 @@ +1.0.4.zip \ No newline at end of file diff --git a/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.3.zip b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.3.zip new file mode 100644 index 0000000..28212ee --- /dev/null +++ b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.3.zip @@ -0,0 +1 @@ +BROKEN ZIP diff --git a/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.4.zip b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.4.zip new file mode 100644 index 0000000..d16cdc9 --- /dev/null +++ b/test/issue1336-registry/packages/gitcompatibledubpackage/1.0.4.zip Binary files differ diff --git a/test/issue1350-transitive-none-deps/.gitignore b/test/issue1350-transitive-none-deps/.gitignore new file mode 100644 index 0000000..4571d26 --- /dev/null +++ b/test/issue1350-transitive-none-deps/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +test +test.so +test.dylib +test.dll +test.a +test.lib +test-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1350-transitive-none-deps/.no_run b/test/issue1350-transitive-none-deps/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1350-transitive-none-deps/.no_run diff --git a/test/issue1350-transitive-none-deps/.no_test b/test/issue1350-transitive-none-deps/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1350-transitive-none-deps/.no_test diff --git a/test/issue1350-transitive-none-deps/common-dep/common.d b/test/issue1350-transitive-none-deps/common-dep/common.d new file mode 100644 index 0000000..23cc13c --- /dev/null +++ b/test/issue1350-transitive-none-deps/common-dep/common.d @@ -0,0 +1 @@ +module common; diff --git a/test/issue1350-transitive-none-deps/common-dep/dub.sdl b/test/issue1350-transitive-none-deps/common-dep/dub.sdl new file mode 100644 index 0000000..e4d29b2 --- /dev/null +++ b/test/issue1350-transitive-none-deps/common-dep/dub.sdl @@ -0,0 +1,4 @@ +name "common-dep" +targetType "library" +importPaths "." +sourceFiles "common.d" diff --git a/test/issue1350-transitive-none-deps/common-none/dub.sdl b/test/issue1350-transitive-none-deps/common-none/dub.sdl new file mode 100644 index 0000000..79986cc --- /dev/null +++ b/test/issue1350-transitive-none-deps/common-none/dub.sdl @@ -0,0 +1,3 @@ +name "common-none" +targetType "none" +dependency "common-dep" path="../common-dep" diff --git a/test/issue1350-transitive-none-deps/dep1/dep1.d b/test/issue1350-transitive-none-deps/dep1/dep1.d new file mode 100644 index 0000000..6b47d60 --- /dev/null +++ b/test/issue1350-transitive-none-deps/dep1/dep1.d @@ -0,0 +1,2 @@ +module dep1; +import common; diff --git a/test/issue1350-transitive-none-deps/dep1/dub.sdl b/test/issue1350-transitive-none-deps/dep1/dub.sdl new file mode 100644 index 0000000..432097a --- /dev/null +++ b/test/issue1350-transitive-none-deps/dep1/dub.sdl @@ -0,0 +1,4 @@ +name "dep1" +importPaths "." +sourceFiles "dep1.d" +dependency "common-none" path="../common-none" diff --git a/test/issue1350-transitive-none-deps/dep2/dep2.d b/test/issue1350-transitive-none-deps/dep2/dep2.d new file mode 100644 index 0000000..fbfed0d --- /dev/null +++ b/test/issue1350-transitive-none-deps/dep2/dep2.d @@ -0,0 +1,2 @@ +module dep2; +import common; diff --git a/test/issue1350-transitive-none-deps/dep2/dub.sdl b/test/issue1350-transitive-none-deps/dep2/dub.sdl new file mode 100644 index 0000000..3157e08 --- /dev/null +++ b/test/issue1350-transitive-none-deps/dep2/dub.sdl @@ -0,0 +1,4 @@ +name "dep2" +importPaths "." +sourceFiles "dep2.d" +dependency "common-none" path="../common-none" diff --git a/test/issue1350-transitive-none-deps/dub.sdl b/test/issue1350-transitive-none-deps/dub.sdl new file mode 100644 index 0000000..5bcd940 --- /dev/null +++ b/test/issue1350-transitive-none-deps/dub.sdl @@ -0,0 +1,5 @@ +name "test" +targetType "executable" +sourceFiles "test.d" +dependency "dep1" path="dep1" +dependency "dep2" path="dep2" diff --git a/test/issue1350-transitive-none-deps/test.d b/test/issue1350-transitive-none-deps/test.d new file mode 100644 index 0000000..4390778 --- /dev/null +++ b/test/issue1350-transitive-none-deps/test.d @@ -0,0 +1,5 @@ +module test; + +import dep1, dep2, common; + +void main() {} diff --git a/test/issue1396-pre-post-run-commands.sh b/test/issue1396-pre-post-run-commands.sh new file mode 100755 index 0000000..fdb797e --- /dev/null +++ b/test/issue1396-pre-post-run-commands.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/issue1396-pre-post-run-commands +rm -rf .dub +rm -rf test.txt +"$DUB" + +if ! grep -c -e "pre-run" test.txt; then + die $LINENO 'pre run not executed.' +fi + +if ! grep -c -e "post-run-0" test.txt; then + die $LINENO 'post run not executed.' +fi diff --git a/test/issue1396-pre-post-run-commands/.no_build b/test/issue1396-pre-post-run-commands/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1396-pre-post-run-commands/.no_build diff --git a/test/issue1396-pre-post-run-commands/dub.sdl b/test/issue1396-pre-post-run-commands/dub.sdl new file mode 100644 index 0000000..aaad878 --- /dev/null +++ b/test/issue1396-pre-post-run-commands/dub.sdl @@ -0,0 +1,3 @@ +name "test" +preRunCommands "echo pre-run >> test.txt" +postRunCommands "./post-run.sh" \ No newline at end of file diff --git a/test/issue1396-pre-post-run-commands/post-run.sh b/test/issue1396-pre-post-run-commands/post-run.sh new file mode 100755 index 0000000..7b98010 --- /dev/null +++ b/test/issue1396-pre-post-run-commands/post-run.sh @@ -0,0 +1 @@ +echo post-run-$DUB_TARGET_EXIT_STATUS >> test.txt \ No newline at end of file diff --git a/test/issue1396-pre-post-run-commands/source/app.d b/test/issue1396-pre-post-run-commands/source/app.d new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/issue1396-pre-post-run-commands/source/app.d @@ -0,0 +1 @@ +void main() {} diff --git a/test/issue1401-file-system-pkg-supplier/.no_build b/test/issue1401-file-system-pkg-supplier/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1401-file-system-pkg-supplier/.no_build diff --git a/test/issue1401-file-system-pkg-supplier/fs-json-dubpackage-1.0.7.zip b/test/issue1401-file-system-pkg-supplier/fs-json-dubpackage-1.0.7.zip new file mode 100644 index 0000000..1b9915e --- /dev/null +++ b/test/issue1401-file-system-pkg-supplier/fs-json-dubpackage-1.0.7.zip Binary files differ diff --git a/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.5.zip b/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.5.zip new file mode 100644 index 0000000..6e58d2f --- /dev/null +++ b/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.5.zip Binary files differ diff --git a/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.6.zip b/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.6.zip new file mode 100644 index 0000000..76f3dcf --- /dev/null +++ b/test/issue1401-file-system-pkg-supplier/fs-sdl-dubpackage-1.0.6.zip Binary files differ diff --git a/test/issue1401-filesystem-supplier.sh b/test/issue1401-filesystem-supplier.sh new file mode 100755 index 0000000..1367fc6 --- /dev/null +++ b/test/issue1401-filesystem-supplier.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +dub remove fs-json-dubpackage --non-interactive --version=* 2>/dev/null || true +dub remove fs-sdl-dubpackage --non-interactive --version=* 2>/dev/null || true + +echo "Trying to get fs-sdl-dubpackage (1.0.5)" +"$DUB" fetch fs-sdl-dubpackage --version=1.0.5 --skip-registry=all --registry=file://"$DIR"/issue1401-file-system-pkg-supplier + +if ! dub remove fs-sdl-dubpackage --non-interactive --version=1.0.5 2>/dev/null; then + die 'DUB did not install package from file system.' +fi + +echo "Trying to get fs-sdl-dubpackage (latest)" +"$DUB" fetch fs-sdl-dubpackage --skip-registry=all --registry=file://"$DIR"/issue1401-file-system-pkg-supplier + +if ! dub remove fs-sdl-dubpackage --non-interactive --version=1.0.6 2>/dev/null; then + die 'DUB did not install latest package from file system.' +fi + +echo "Trying to get fs-json-dubpackage (1.0.7)" +"$DUB" fetch fs-json-dubpackage --version=1.0.7 --skip-registry=all --registry=file://"$DIR"/issue1401-file-system-pkg-supplier + +if ! dub remove fs-json-dubpackage --non-interactive --version=1.0.7 2>/dev/null; then + die 'DUB did not install package from file system.' +fi diff --git a/test/issue1408-inherit-linker-files/.no_run b/test/issue1408-inherit-linker-files/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1408-inherit-linker-files/.no_run diff --git a/test/issue1408-inherit-linker-files/.no_test b/test/issue1408-inherit-linker-files/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1408-inherit-linker-files/.no_test diff --git a/test/issue1408-inherit-linker-files/dep.d b/test/issue1408-inherit-linker-files/dep.d new file mode 100644 index 0000000..d0a80bf --- /dev/null +++ b/test/issue1408-inherit-linker-files/dep.d @@ -0,0 +1 @@ +module dep; diff --git a/test/issue1408-inherit-linker-files/dub.sdl b/test/issue1408-inherit-linker-files/dub.sdl new file mode 100644 index 0000000..4aaaed8 --- /dev/null +++ b/test/issue1408-inherit-linker-files/dub.sdl @@ -0,0 +1,14 @@ +name "test" +targetType "executable" +dependency ":dep" version="*" +sourceFiles "main.d" +// make lib.d available for import +importPaths "." + +subPackage { + name "dep" + sourceFiles "dep.d" + sourceFiles "lib/liblib.a" platform="posix" + sourceFiles "lib/lib.lib" platform="windows" + preBuildCommands "dub build --root=\"$PACKAGE_DIR/lib\"" +} diff --git a/test/issue1408-inherit-linker-files/lib.d b/test/issue1408-inherit-linker-files/lib.d new file mode 100644 index 0000000..0f143cf --- /dev/null +++ b/test/issue1408-inherit-linker-files/lib.d @@ -0,0 +1,3 @@ +module lib; + +void foo(); diff --git a/test/issue1408-inherit-linker-files/lib/dub.sdl b/test/issue1408-inherit-linker-files/lib/dub.sdl new file mode 100644 index 0000000..0706e67 --- /dev/null +++ b/test/issue1408-inherit-linker-files/lib/dub.sdl @@ -0,0 +1,3 @@ +name "lib" +targetType "staticLibrary" +sourceFiles "lib.d" diff --git a/test/issue1408-inherit-linker-files/lib/lib.d b/test/issue1408-inherit-linker-files/lib/lib.d new file mode 100644 index 0000000..22c8012 --- /dev/null +++ b/test/issue1408-inherit-linker-files/lib/lib.d @@ -0,0 +1,5 @@ +module lib; + +void foo() +{ +} diff --git a/test/issue1408-inherit-linker-files/main.d b/test/issue1408-inherit-linker-files/main.d new file mode 100644 index 0000000..06383a2 --- /dev/null +++ b/test/issue1408-inherit-linker-files/main.d @@ -0,0 +1,6 @@ +import lib; + +void main() +{ + foo(); +} diff --git a/test/issue1416-maven-repo-pkg-supplier.sh b/test/issue1416-maven-repo-pkg-supplier.sh new file mode 100755 index 0000000..4f106b4 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +dub remove maven-dubpackage --non-interactive --version=* 2>/dev/null || true + +"$DUB" build --single "$DIR"/test_registry.d +"$DIR"/test_registry --folder="$DIR/issue1416-maven-repo-pkg-supplier" --port=$PORT & +PID=$! +sleep 1 +trap 'kill $PID 2>/dev/null || true' exit + +echo "Trying to download maven-dubpackage (1.0.5)" +"$DUB" fetch maven-dubpackage --version=1.0.5 --skip-registry=all --registry=mvn+http://localhost:$PORT/maven/release/dubpackages + +if ! dub remove maven-dubpackage --non-interactive --version=1.0.5 2>/dev/null; then + die 'DUB did not install package from maven registry.' +fi + +echo "Trying to download maven-dubpackage (latest)" +"$DUB" fetch maven-dubpackage --skip-registry=all --registry=mvn+http://localhost:$PORT/maven/release/dubpackages + +if ! dub remove maven-dubpackage --non-interactive --version=1.0.6 2>/dev/null; then + die 'DUB did not install latest package from maven registry.' +fi \ No newline at end of file diff --git a/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend b/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend new file mode 100644 index 0000000..bb0a2e1 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier.sh.min_frontend @@ -0,0 +1 @@ +2.076 diff --git a/test/issue1416-maven-repo-pkg-supplier/.gitignore b/test/issue1416-maven-repo-pkg-supplier/.gitignore new file mode 100644 index 0000000..304e955 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/.gitignore @@ -0,0 +1,8 @@ +.dub +docs.json +__dummy.html +docs/ +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1416-maven-repo-pkg-supplier/.no_build b/test/issue1416-maven-repo-pkg-supplier/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/.no_build diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip new file mode 100644 index 0000000..c3e7d58 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.5/maven-dubpackage-1.0.5.zip Binary files differ diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip new file mode 100644 index 0000000..333d920 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/1.0.6/maven-dubpackage-1.0.6.zip Binary files differ diff --git a/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml new file mode 100644 index 0000000..7b171d5 --- /dev/null +++ b/test/issue1416-maven-repo-pkg-supplier/maven/release/dubpackages/maven-dubpackage/maven-metadata.xml @@ -0,0 +1,14 @@ + + + dubpackages + maven-dubpackage + + 1.0.6 + 1.0.6 + + 1.0.5 + 1.0.6 + + 20180317184845 + + \ No newline at end of file diff --git a/test/issue1427-betterC/.gitignore b/test/issue1427-betterC/.gitignore new file mode 100644 index 0000000..0fd9d37 --- /dev/null +++ b/test/issue1427-betterC/.gitignore @@ -0,0 +1,4 @@ +test +*.o +*.exe +.dub \ No newline at end of file diff --git a/test/issue1427-betterC/.min_frontend b/test/issue1427-betterC/.min_frontend new file mode 100644 index 0000000..67aaf4a --- /dev/null +++ b/test/issue1427-betterC/.min_frontend @@ -0,0 +1 @@ +2.078 diff --git a/test/issue1427-betterC/.no_run b/test/issue1427-betterC/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1427-betterC/.no_run diff --git a/test/issue1427-betterC/.no_test b/test/issue1427-betterC/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1427-betterC/.no_test diff --git a/test/issue1427-betterC/dub.json b/test/issue1427-betterC/dub.json new file mode 100644 index 0000000..d6e9256 --- /dev/null +++ b/test/issue1427-betterC/dub.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "buildOptions": ["betterC"] +} \ No newline at end of file diff --git a/test/issue1427-betterC/source/app.d b/test/issue1427-betterC/source/app.d new file mode 100644 index 0000000..ca1a7ca --- /dev/null +++ b/test/issue1427-betterC/source/app.d @@ -0,0 +1,3 @@ +version(D_BetterC) {} else static assert(false); + +extern(C) void main() { } \ No newline at end of file diff --git a/test/issue1447-build-settings-vars.sh b/test/issue1447-build-settings-vars.sh new file mode 100755 index 0000000..5f60e06 --- /dev/null +++ b/test/issue1447-build-settings-vars.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +if [[ `uname -m` == "x86_64" ]]; then + ARCH=x86_64 +else + ARCH=x86 +fi + +rm -rf ${CURR_DIR}/issue1447-build-settings-vars/.dub +rm -rf ${CURR_DIR}/issue1447-build-settings-vars/test + +${DUB} build --root ${CURR_DIR}/issue1447-build-settings-vars --arch=$ARCH +OUTPUT=`${CURR_DIR}/issue1447-build-settings-vars/test` + +rm -rf ${CURR_DIR}/issue1447-build-settings-vars/.dub +rm -rf ${CURR_DIR}/issue1447-build-settings-vars/test + +if [[ "$OUTPUT" != "$ARCH" ]]; then die "Build settings ARCH var incorrect"; fi diff --git a/test/issue1447-build-settings-vars/.no_run b/test/issue1447-build-settings-vars/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1447-build-settings-vars/.no_run diff --git a/test/issue1447-build-settings-vars/.no_test b/test/issue1447-build-settings-vars/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1447-build-settings-vars/.no_test diff --git a/test/issue1447-build-settings-vars/dub.json b/test/issue1447-build-settings-vars/dub.json new file mode 100644 index 0000000..9f5d345 --- /dev/null +++ b/test/issue1447-build-settings-vars/dub.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "stringImportPaths": ["view-$ARCH"] +} \ No newline at end of file diff --git a/test/issue1447-build-settings-vars/source/app.d b/test/issue1447-build-settings-vars/source/app.d new file mode 100644 index 0000000..3e94dfc --- /dev/null +++ b/test/issue1447-build-settings-vars/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln(import("arch")); +} diff --git a/test/issue1447-build-settings-vars/view-x86/arch b/test/issue1447-build-settings-vars/view-x86/arch new file mode 100644 index 0000000..f4bad79 --- /dev/null +++ b/test/issue1447-build-settings-vars/view-x86/arch @@ -0,0 +1 @@ +x86 \ No newline at end of file diff --git a/test/issue1447-build-settings-vars/view-x86_64/arch b/test/issue1447-build-settings-vars/view-x86_64/arch new file mode 100644 index 0000000..8790996 --- /dev/null +++ b/test/issue1447-build-settings-vars/view-x86_64/arch @@ -0,0 +1 @@ +x86_64 \ No newline at end of file diff --git a/test/issue1504-envvar-in-path.sh b/test/issue1504-envvar-in-path.sh new file mode 100755 index 0000000..51cb805 --- /dev/null +++ b/test/issue1504-envvar-in-path.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -e + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +rm -rf ${CURR_DIR}/issue1504-envvar-in-path/.dub +rm -rf ${CURR_DIR}/issue1504-envvar-in-path/test +rm -rf ${CURR_DIR}/output-1504.txt + + +export MY_VARIABLE=teststrings +# pragma(msg) outputs to stderr +${DUB} build --force --root ${CURR_DIR}/issue1504-envvar-in-path 2> ${CURR_DIR}/output-1504.txt + +grep "env_variables_work" < ${CURR_DIR}/output-1504.txt + +# Don't manage to make it work +#grep "Invalid source" < ${CURR_DIR}/output-1504.txt && true + diff --git a/test/issue1504-envvar-in-path/.no_build b/test/issue1504-envvar-in-path/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1504-envvar-in-path/.no_build diff --git a/test/issue1504-envvar-in-path/dub.json b/test/issue1504-envvar-in-path/dub.json new file mode 100644 index 0000000..1858aa6 --- /dev/null +++ b/test/issue1504-envvar-in-path/dub.json @@ -0,0 +1,5 @@ +{ + "name": "test", + "stringImportPaths": ["$MY_VARIABLE"] +} + diff --git a/test/issue1504-envvar-in-path/source/app.d b/test/issue1504-envvar-in-path/source/app.d new file mode 100644 index 0000000..131d984 --- /dev/null +++ b/test/issue1504-envvar-in-path/source/app.d @@ -0,0 +1,5 @@ +pragma(msg, import("message.txt")); + +void main() +{ +} \ No newline at end of file diff --git a/test/issue1504-envvar-in-path/teststrings/message.txt b/test/issue1504-envvar-in-path/teststrings/message.txt new file mode 100644 index 0000000..88f0c20 --- /dev/null +++ b/test/issue1504-envvar-in-path/teststrings/message.txt @@ -0,0 +1 @@ +env_variables_work \ No newline at end of file diff --git a/test/issue1524-maven-upgrade-dependency-tree.sh b/test/issue1524-maven-upgrade-dependency-tree.sh new file mode 100755 index 0000000..711cded --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +dub remove maven-dubpackage-a --non-interactive --version=* 2>/dev/null || true +dub remove maven-dubpackage-b --non-interactive --version=* 2>/dev/null || true + +"$DUB" build --single "$DIR"/test_registry.d +"$DIR"/test_registry --folder="$DIR/issue1524-maven-upgrade-dependency-tree" --port=$PORT & +PID=$! +sleep 1 +trap 'kill $PID 2>/dev/null || true' exit + +echo "Trying to download maven-dubpackage-a (1.0.5) with dependency to maven-dubpackage-b (1.0.6)" +"$DUB" upgrade --root "$DIR/issue1524-maven-upgrade-dependency-tree" --skip-registry=standard --registry=mvn+http://localhost:$PORT/maven/release/dubpackages + +if ! dub remove maven-dubpackage-a --non-interactive --version=1.0.5 2>/dev/null; then + die 'DUB did not install package "maven-dubpackage-a" from maven registry.' +fi + +if ! dub remove maven-dubpackage-b --non-interactive --version=1.0.6 2>/dev/null; then + die 'DUB did not install package "maven-dubpackage-b" from maven registry.' +fi + diff --git a/test/issue1524-maven-upgrade-dependency-tree.sh.min_frontend b/test/issue1524-maven-upgrade-dependency-tree.sh.min_frontend new file mode 100644 index 0000000..bb0a2e1 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree.sh.min_frontend @@ -0,0 +1 @@ +2.076 diff --git a/test/issue1524-maven-upgrade-dependency-tree/.gitignore b/test/issue1524-maven-upgrade-dependency-tree/.gitignore new file mode 100644 index 0000000..304e955 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/.gitignore @@ -0,0 +1,8 @@ +.dub +docs.json +__dummy.html +docs/ +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1524-maven-upgrade-dependency-tree/.no_build b/test/issue1524-maven-upgrade-dependency-tree/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/.no_build diff --git a/test/issue1524-maven-upgrade-dependency-tree/dub.json b/test/issue1524-maven-upgrade-dependency-tree/dub.json new file mode 100644 index 0000000..131a264 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/dub.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "maven-dubpackage-a": "~>1.0.5" + } +} \ No newline at end of file diff --git a/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/1.0.5/maven-dubpackage-a-1.0.5.zip b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/1.0.5/maven-dubpackage-a-1.0.5.zip new file mode 100644 index 0000000..6d5cfc9 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/1.0.5/maven-dubpackage-a-1.0.5.zip Binary files differ diff --git a/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/maven-metadata.xml b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/maven-metadata.xml new file mode 100644 index 0000000..b3202ee --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-a/maven-metadata.xml @@ -0,0 +1,13 @@ + + + dubpackages + maven-dubpackage-a + + 1.0.5 + 1.0.5 + + 1.0.5 + + 20180317184845 + + \ No newline at end of file diff --git a/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/1.0.6/maven-dubpackage-b-1.0.6.zip b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/1.0.6/maven-dubpackage-b-1.0.6.zip new file mode 100644 index 0000000..c793c0b --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/1.0.6/maven-dubpackage-b-1.0.6.zip Binary files differ diff --git a/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/maven-metadata.xml b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/maven-metadata.xml new file mode 100644 index 0000000..f7dcd2a --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/maven/release/dubpackages/maven-dubpackage-b/maven-metadata.xml @@ -0,0 +1,13 @@ + + + dubpackages + maven-dubpackage-b + + 1.0.6 + 1.0.6 + + 1.0.6 + + 20180317184845 + + \ No newline at end of file diff --git a/test/issue1524-maven-upgrade-dependency-tree/source/app.d b/test/issue1524-maven-upgrade-dependency-tree/source/app.d new file mode 100644 index 0000000..ef93217 --- /dev/null +++ b/test/issue1524-maven-upgrade-dependency-tree/source/app.d @@ -0,0 +1 @@ +void main(){} \ No newline at end of file diff --git a/test/issue1531-toolchain-requirements.sh b/test/issue1531-toolchain-requirements.sh new file mode 100755 index 0000000..c384198 --- /dev/null +++ b/test/issue1531-toolchain-requirements.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -e + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cat << EOF | $DUB - || die "Did not pass without toolchainRequirements" +/+ dub.sdl: ++/ +void main() {} +EOF + +# pass test dub requirement given as $1 +function test_dub_req_pass { + cat << EOF | $DUB - || die "Did not pass requirement dub=\"$1\"" +/+ dub.sdl: + toolchainRequirements dub="$1" ++/ +void main() {} +EOF +} + +# fail test dub requirement given as $1 +function test_dub_req_fail { + ! cat << EOF | $DUB - || die "Did not pass requirement dub=\"$1\"" +/+ dub.sdl: + toolchainRequirements dub="$1" ++/ +void main() {} +EOF +} + +test_dub_req_pass ">=1.7.0" +test_dub_req_fail "~>0.9" +test_dub_req_fail "~>999.0" + +# extract compiler version +if [[ $DC == *ldc* ]] || [[ $DC == *ldmd* ]]; then + VER_REG='\((([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))\)' + DC_NAME=ldc +elif [[ $DC == *dmd* ]]; then + VER_REG='v(([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))' + DC_NAME=dmd +elif [[ $DC == *gdc* ]]; then + VER_REG='\) (([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))' + DC_NAME=gdc +else + die "Did not recognize compiler" +fi +if [[ $($DC --version) =~ $VER_REG ]]; then + DC_VER=${BASH_REMATCH[1]} + DC_VER_MAJ=${BASH_REMATCH[2]} + DC_VER_REM=${BASH_REMATCH[3]} + $DC --version + echo $DC version is $DC_VER +else + $DC --version + die "Could not extract compiler version" +fi + +# create test app directory +TMPDIR=$(mktemp -d /tmp/dubtest1531_XXXXXX) +mkdir -p $TMPDIR/source +cat << EOF > $TMPDIR/source/app.d +module dubtest1531; +void main() {} +EOF + +# write dub.sdl with compiler requirement given as $1 +function write_cl_req { + cat << EOF > $TMPDIR/dub.sdl +name "dubtest1531" +toolchainRequirements ${DC_NAME}="$1" +EOF +} + +# pass test compiler requirement given as $1 +function test_cl_req_pass { + write_cl_req $1 + $DUB --compiler=$DC --root=$TMPDIR || die "Did not pass with $DC_NAME=\"$1\"" +} + +# fail test compiler requirement given as $1 +function test_cl_req_fail { + write_cl_req $1 + ! $DUB --compiler=$DC --root=$TMPDIR || die "Did not fail with $DC_NAME=\"$1\"" +} + + +test_cl_req_pass "==$DC_VER" +test_cl_req_pass ">=$DC_VER" +test_cl_req_fail ">$DC_VER" +test_cl_req_pass "<=$DC_VER" +test_cl_req_fail "<$DC_VER" +test_cl_req_pass ">=$DC_VER <$(($DC_VER_MAJ + 1))$DC_VER_REM" +test_cl_req_pass "~>$DC_VER" +test_cl_req_fail "~>$(($DC_VER_MAJ + 1))$DC_VER_REM" +test_cl_req_fail no + +rm -rf $TMPDIR diff --git a/test/issue1551-var-escaping/dub.json b/test/issue1551-var-escaping/dub.json new file mode 100644 index 0000000..f24ca66 --- /dev/null +++ b/test/issue1551-var-escaping/dub.json @@ -0,0 +1,7 @@ +{ + "name": "issue1551-var-escaping", + "preGenerateCommands": [ + "echo $${DUB_PACKAGE_DIR}", + "echo $$DUB_PACKAGE_DIR" + ] +} diff --git a/test/issue1551-var-escaping/source/app.d b/test/issue1551-var-escaping/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue1551-var-escaping/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue1574-addcommand.sh b/test/issue1574-addcommand.sh new file mode 100755 index 0000000..dddedca --- /dev/null +++ b/test/issue1574-addcommand.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +DIR=$(dirname "${BASH_SOURCE[0]}") + +. "$DIR"/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 +tempDir="issue1574-addcommand" + +"$DUB" build --single "$DIR"/test_registry.d +"$DIR"/test_registry --folder="$DIR/issue1336-registry" --port=$PORT & +PID=$! +sleep 1 + +function cleanup { + cd .. + rm -rf $tempDir + kill $PID 2>/dev/null || true +} +trap cleanup EXIT + + +$DUB init --non-interactive --format=json $tempDir +cd $tempDir + +echo "import gitcompatibledubpackage.subdir.file; void main(){}" > source/app.d +$DUB add gitcompatibledubpackage --skip-registry=all --registry=http://localhost:$PORT +grep -q '"gitcompatibledubpackage"\s*:\s*"~>1\.0\.4"' dub.json +$DUB add gitcompatibledubpackage=1.0.2 non-existing-issue1574-pkg='~>9.8.7' --skip-registry=all +grep -q '"gitcompatibledubpackage"\s*:\s*"1\.0\.2"' dub.json +grep -q '"non-existing-issue1574-pkg"\s*:\s*"~>9\.8\.7"' dub.json +if $DUB add foo@1.2.3 gitcompatibledubpackage='~>a.b.c' --skip-registry=all; then + die $LINENO 'Adding non-semver spec should error' +fi +if grep -q '"foo"' dub.json; then + die $LINENO 'Failing add command should not write recipe file' +fi diff --git a/test/issue1574-addcommand.sh.min_frontend b/test/issue1574-addcommand.sh.min_frontend new file mode 100644 index 0000000..bb0a2e1 --- /dev/null +++ b/test/issue1574-addcommand.sh.min_frontend @@ -0,0 +1 @@ +2.076 diff --git a/test/issue1651-custom-dub-init-type.sh b/test/issue1651-custom-dub-init-type.sh new file mode 100755 index 0000000..5c48313 --- /dev/null +++ b/test/issue1651-custom-dub-init-type.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh +DIR=$(dirname "${BASH_SOURCE[0]}") +packname="custom-dub-init-type-sample" + +$DUB remove custom-dub-init-dubpackage --non-interactive --version=* 2>/dev/null || true +$DUB init -n $packname --format sdl -t custom-dub-init-dubpackage --skip-registry=all --registry=file://"$DIR"/issue1651-custom-dub-init-type -- --foo=bar + +function cleanup { + rm -rf $packname +} + +if [ ! -e $packname/dub.sdl ]; then # it failed + cleanup + die $LINENO 'No dub.sdl file has been generated.' +fi + +cd $packname +if ! { ${DUB} 2>&1 || true; } | grep -cF 'foo=bar'; then + cd .. + cleanup + die $LINENO 'Custom init type.' +fi +cd .. +cleanup \ No newline at end of file diff --git a/test/issue1651-custom-dub-init-type/.no_build b/test/issue1651-custom-dub-init-type/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1651-custom-dub-init-type/.no_build diff --git a/test/issue1651-custom-dub-init-type/.no_run b/test/issue1651-custom-dub-init-type/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1651-custom-dub-init-type/.no_run diff --git a/test/issue1651-custom-dub-init-type/.no_test b/test/issue1651-custom-dub-init-type/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1651-custom-dub-init-type/.no_test diff --git a/test/issue1651-custom-dub-init-type/custom-dub-init-dubpackage-1.0.1.zip b/test/issue1651-custom-dub-init-type/custom-dub-init-dubpackage-1.0.1.zip new file mode 100644 index 0000000..67a499b --- /dev/null +++ b/test/issue1651-custom-dub-init-type/custom-dub-init-dubpackage-1.0.1.zip Binary files differ diff --git a/test/issue1691-build-subpkg.sh b/test/issue1691-build-subpkg.sh new file mode 100755 index 0000000..ea70068 --- /dev/null +++ b/test/issue1691-build-subpkg.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh +$DUB build --root="$CURR_DIR/issue1691-build-subpkg" :subpkg diff --git a/test/issue1691-build-subpkg/.gitignore b/test/issue1691-build-subpkg/.gitignore new file mode 100644 index 0000000..1ba3960 --- /dev/null +++ b/test/issue1691-build-subpkg/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +/issue1691-build-subpkg +issue1691-build-subpkg.so +issue1691-build-subpkg.dylib +issue1691-build-subpkg.dll +issue1691-build-subpkg.a +issue1691-build-subpkg.lib +issue1691-build-subpkg-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/issue1691-build-subpkg/.no_build b/test/issue1691-build-subpkg/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1691-build-subpkg/.no_build diff --git a/test/issue1691-build-subpkg/dub.sdl b/test/issue1691-build-subpkg/dub.sdl new file mode 100644 index 0000000..e7ab799 --- /dev/null +++ b/test/issue1691-build-subpkg/dub.sdl @@ -0,0 +1,3 @@ +name "issue1691-build-subpkg" +dependency ":subpkg" version="*" +subPackage "./subpkg/" diff --git a/test/issue1691-build-subpkg/source/app.d b/test/issue1691-build-subpkg/source/app.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1691-build-subpkg/source/app.d diff --git a/test/issue1691-build-subpkg/subpkg/dub.sdl b/test/issue1691-build-subpkg/subpkg/dub.sdl new file mode 100644 index 0000000..25c3c2a --- /dev/null +++ b/test/issue1691-build-subpkg/subpkg/dub.sdl @@ -0,0 +1 @@ +name "subpkg" diff --git a/test/issue1691-build-subpkg/subpkg/source/subpkg.d b/test/issue1691-build-subpkg/subpkg/source/subpkg.d new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue1691-build-subpkg/subpkg/source/subpkg.d diff --git a/test/issue346-redundant-flags.sh b/test/issue346-redundant-flags.sh index fb88526..c6b27f2 100755 --- a/test/issue346-redundant-flags.sh +++ b/test/issue346-redundant-flags.sh @@ -1,4 +1,5 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue346-redundant-flags -${DUB} build --bare --force --compiler=${DC} -a x86_64 -v main 2>&1 | grep -e "-m64 -m64" -c && exit 1 || exit 0 +${DUB} build --bare --force --compiler=${DC} -a x86_64 -v main 2>&1 | { ! grep -e '-m64 -m64' -c; } diff --git a/test/issue361-optional-deps.sh b/test/issue361-optional-deps.sh index 69b7f0f..db87794 100755 --- a/test/issue361-optional-deps.sh +++ b/test/issue361-optional-deps.sh @@ -1,5 +1,6 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue361-optional-deps rm -rf a/.dub rm -rf a/b/.dub @@ -7,20 +8,20 @@ rm -rf main2/.dub rm -f main1/dub.selections.json -${DUB} build --bare --compiler=${DC} main1 || exit 1 +${DUB} build --bare --compiler=${DC} main1 echo "{" > cmp.tmp echo " \"fileVersion\": 1," >> cmp.tmp echo " \"versions\": {" >> cmp.tmp echo " \"b\": \"~master\"" >> cmp.tmp echo " }" >> cmp.tmp echo "}" >> cmp.tmp -diff cmp.tmp main1/dub.selections.json || exit 1 +diff cmp.tmp main1/dub.selections.json -${DUB} build --bare --compiler=${DC} main2 || exit 1 +${DUB} build --bare --compiler=${DC} main2 echo "{" > cmp.tmp echo " \"fileVersion\": 1," >> cmp.tmp echo " \"versions\": {" >> cmp.tmp echo " \"a\": \"~master\"" >> cmp.tmp echo " }" >> cmp.tmp echo "}" >> cmp.tmp -diff cmp.tmp main2/dub.selections.json || exit 1 +diff cmp.tmp main2/dub.selections.json diff --git a/test/issue564-invalid-upgrade-dependency.sh b/test/issue564-invalid-upgrade-dependency.sh index a1af6bd..19258ce 100755 --- a/test/issue564-invalid-upgrade-dependency.sh +++ b/test/issue564-invalid-upgrade-dependency.sh @@ -1,7 +1,8 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue564-invalid-upgrade-dependency rm -rf a-1.0.0/.dub rm -rf a-1.1.0/.dub rm -rf main/.dub -${DUB} build --bare --compiler=${DC} main || exit 1 +${DUB} build --bare --compiler=${DC} main diff --git a/test/issue586-subpack-dep.sh b/test/issue586-subpack-dep.sh index fe0ad5f..306bca7 100755 --- a/test/issue586-subpack-dep.sh +++ b/test/issue586-subpack-dep.sh @@ -1,8 +1,9 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue586-subpack-dep rm -rf a/.dub rm -rf a/b/.dub rm -rf main/.dub -${DUB} build --bare --compiler=${DC} main || exit 1 -${DUB} run --bare --compiler=${DC} main || exit 1 +${DUB} build --bare --compiler=${DC} main +${DUB} run --bare --compiler=${DC} main diff --git a/test/issue613-dynlib-pic.sh b/test/issue613-dynlib-pic.sh index d7e90b7..b8fc5e7 100755 --- a/test/issue613-dynlib-pic.sh +++ b/test/issue613-dynlib-pic.sh @@ -1,10 +1,10 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue613-dynlib-pic rm -rf .dub if [ "${DC}" = "dmd" ]; then - ${DUB} build --compiler=${DC} || exit 1 + ${DUB} build --compiler=${DC} else echo "Skipping shared library test for ${DC}..." fi - diff --git a/test/issue613-dynlib-pic/source/app.d b/test/issue613-dynlib-pic/source/app.d index 26a3f4d..8b92d48 100644 --- a/test/issue613-dynlib-pic/source/app.d +++ b/test/issue613-dynlib-pic/source/app.d @@ -1,4 +1,4 @@ void test() { - + } \ No newline at end of file diff --git a/test/issue616-describe-vs-generate-commands.sh b/test/issue616-describe-vs-generate-commands.sh index 8f85c09..698ef63 100755 --- a/test/issue616-describe-vs-generate-commands.sh +++ b/test/issue616-describe-vs-generate-commands.sh @@ -1,5 +1,5 @@ -#!/bin/bash -set -e -o pipefail +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd "$CURR_DIR"/issue616-describe-vs-generate-commands diff --git a/test/issue616-describe-vs-generate-commands/do-preGenerateCommands.sh b/test/issue616-describe-vs-generate-commands/do-preGenerateCommands.sh index 84468cd..6342d76 100755 --- a/test/issue616-describe-vs-generate-commands/do-preGenerateCommands.sh +++ b/test/issue616-describe-vs-generate-commands/do-preGenerateCommands.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash if [ -n "${dub_issue616}" ]; then echo 'Fail! preGenerateCommands recursion detected!' >&2 exit 0 # Don't return a non-zero error code here. This way the test gives a better diagnostic. diff --git a/test/issue616-describe-vs-generate-commands/src/dummy.d b/test/issue616-describe-vs-generate-commands/src/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/issue616-describe-vs-generate-commands/src/dummy.d +++ b/test/issue616-describe-vs-generate-commands/src/dummy.d @@ -1 +1 @@ - + diff --git a/test/issue616-subpack/dub.json b/test/issue616-subpack/dub.json index 552ddcd..31c525a 100644 --- a/test/issue616-subpack/dub.json +++ b/test/issue616-subpack/dub.json @@ -1,6 +1,6 @@ { "name": "issue616-subpack", - "targetType": "executable", + "targetType": "library", "dependencies": { "issue616-subsubpack": { "version": "1.0", diff --git a/test/issue616-subpack/src/dummy.d b/test/issue616-subpack/src/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/issue616-subpack/src/dummy.d +++ b/test/issue616-subpack/src/dummy.d @@ -1 +1 @@ - + diff --git a/test/issue616-subsubpack/dub.json b/test/issue616-subsubpack/dub.json index e4e4b5b..207761f 100644 --- a/test/issue616-subsubpack/dub.json +++ b/test/issue616-subsubpack/dub.json @@ -1,4 +1,4 @@ { "name": "issue616-subsubpack", - "targetType": "executable" + "targetType": "library" } diff --git a/test/issue616-subsubpack/src/dummy.d b/test/issue616-subsubpack/src/dummy.d index 8d1c8b6..8b13789 100644 --- a/test/issue616-subsubpack/src/dummy.d +++ b/test/issue616-subsubpack/src/dummy.d @@ -1 +1 @@ - + diff --git a/test/issue672-upgrade-optional.sh b/test/issue672-upgrade-optional.sh index 8c16aa6..15e07d2 100755 --- a/test/issue672-upgrade-optional.sh +++ b/test/issue672-upgrade-optional.sh @@ -1,11 +1,11 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue672-upgrade-optional rm -rf b/.dub echo "{\"fileVersion\": 1,\"versions\": {\"dub\": \"1.0.0\"}}" > dub.selections.json -${DUB} upgrade || exit 1 +${DUB} upgrade if ! grep -c -e "\"dub\": \"1.1.0\"" dub.selections.json; then - echo "Dependency not upgraded." - exit 1 + die $LINENO 'Dependency not upgraded.' fi diff --git a/test/issue672-upgrade-optional/dub.selections.json b/test/issue672-upgrade-optional/dub.selections.json index 44b07d4..712a9f6 100644 --- a/test/issue672-upgrade-optional/dub.selections.json +++ b/test/issue672-upgrade-optional/dub.selections.json @@ -1,6 +1,6 @@ { "fileVersion": 1, "versions": { - "dub": "1.0.0" + "dub": "1.1.0" } } diff --git a/test/issue674-concurrent-dub.sh b/test/issue674-concurrent-dub.sh index d1cfadd..d49bdd3 100755 --- a/test/issue674-concurrent-dub.sh +++ b/test/issue674-concurrent-dub.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e -o pipefail +. $(dirname "${BASH_SOURCE[0]}")/common.sh TMPDIR=$(mktemp -d $(basename $0).XXXXXX) @@ -16,6 +16,4 @@ pid2=$! wait $pid1 wait $pid2 -if [ ! -d ${TMPDIR}/bloom* ]; then - exit 1 -fi +[ -d ${TMPDIR}/bloom* ] diff --git a/test/issue686-multiple-march.sh b/test/issue686-multiple-march.sh index cdf2cde..24b84b7 100755 --- a/test/issue686-multiple-march.sh +++ b/test/issue686-multiple-march.sh @@ -1,7 +1,5 @@ -#!/bin/sh +#!/usr/bin/env bash -set -e - +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue686-multiple-march - -${DUB} build --bare --force --compiler=${DC} -a x86_64 -v main 2>&1 | grep -e "-m64 -m64" -c && exit 1 || exit 0 +${DUB} build --bare --force --compiler=${DC} -a x86_64 -v main 2>&1 | { ! grep -e '-m64 -m64' -c; } diff --git a/test/issue782-gtkd-pkg-config.sh b/test/issue782-gtkd-pkg-config.sh index 434ca02..a91d738 100755 --- a/test/issue782-gtkd-pkg-config.sh +++ b/test/issue782-gtkd-pkg-config.sh @@ -1,5 +1,6 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh if [ "${DC}" != "dmd" ]; then echo "Skipping issue782-dtkd-pkg-config test for ${DC}..." else @@ -7,16 +8,18 @@ # the ${CURR_DIR-$(pwd)} allows running issue782-gtkd-pkg-config.sh stand-alone from the test directory cd ${CURR_DIR-$(pwd)}/issue782-gtkd-pkg-config rm -rf fake-gtkd/.dub - rm fake-gtkd/libfake-gtkd.so + rm -f fake-gtkd/libfake-gtkd.so rm -rf main/.dub - rm main/fake-gtkd-test + rm -f main/fake-gtkd-test echo ${DUB} - cd fake-gtkd && ${DUB} build -v --compiler=${DC} || exit 1 + cd fake-gtkd && ${DUB} build --compiler=${DC} cd ../main # `run` needs to find the fake-gtkd shared library, so set LD_LIBRARY_PATH to where it is - # pkg-config needs to find our .pc file which is in $(pwd)/../fake-gtkd/pkgconfig, so set PKG_CONFIG_PATH accordingly - LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd)/../fake-gtkd PKG_CONFIG_PATH=$(pwd)/../fake-gtkd/pkgconfig ${DUB} -v run --force --compiler=${DC} || exit 1 + export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}${LD_LIBRARY_PATH:+:}$PWD/../fake-gtkd + # pkg-config needs to find our .pc file which is in $PWD/../fake-gtkd/pkgconfig, so set PKG_CONFIG_PATH accordingly + export PKG_CONFIG_PATH=$PWD/../fake-gtkd/pkgconfig + ${DUB} run --force --compiler=${DC} cd .. rm -rf fake-gtkd/.dub rm fake-gtkd/libfake-gtkd.so diff --git a/test/issue782-gtkd-pkg-config/fake-gtkd/pkgconfig/fake-gtkd.pc b/test/issue782-gtkd-pkg-config/fake-gtkd/pkgconfig/fake-gtkd.pc index 1f06032..654ac32 100644 --- a/test/issue782-gtkd-pkg-config/fake-gtkd/pkgconfig/fake-gtkd.pc +++ b/test/issue782-gtkd-pkg-config/fake-gtkd/pkgconfig/fake-gtkd.pc @@ -6,7 +6,7 @@ Description: Fake GtkD shared library for testing Version: 1.0.0 #Requires: phobos2 -# The "-L-defaultlib=libphobos2.so" and "-defaultlib=libphobos2.so" should both end up on the compiler (at link stage) invokation as "-defaultlib=libphobos2.so" +# The "-L-defaultlib=libphobos2.so" and "-defaultlib=libphobos2.so" should both end up on the compiler (at link stage) invocation as "-defaultlib=libphobos2.so" # For this test, it doesn't hurt that they appear twice on the cmd line... Libs: -L-L${libdir} -L-l:libfake-gtkd.so -L-l:libdl.so.2 -pthread -L-defaultlib=libphobos2.so -defaultlib=libphobos2.so Cflags: -I${includedir} diff --git a/test/issue813-fixed-dependency.sh b/test/issue813-fixed-dependency.sh index 2785cdb..bddf078 100755 --- a/test/issue813-fixed-dependency.sh +++ b/test/issue813-fixed-dependency.sh @@ -1,7 +1,8 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue813-fixed-dependency rm -rf main/.dub rm -rf sub/.dub rm -rf sub/sub/.dub -${DUB} build --bare --compiler=${DC} main || exit 1 +${DUB} build --bare --compiler=${DC} main diff --git a/test/issue813-fixed-dependency/main/src/app.d b/test/issue813-fixed-dependency/main/src/app.d index dde0c1e..0b416f0 100644 --- a/test/issue813-fixed-dependency/main/src/app.d +++ b/test/issue813-fixed-dependency/main/src/app.d @@ -3,4 +3,4 @@ void main() { foo(); -} +} diff --git a/test/issue813-pure-sub-dependency.sh b/test/issue813-pure-sub-dependency.sh index 85ab84e..ec2291e 100755 --- a/test/issue813-pure-sub-dependency.sh +++ b/test/issue813-pure-sub-dependency.sh @@ -1,8 +1,9 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue813-pure-sub-dependency rm -rf main/.dub rm -rf sub/.dub rm -rf sub/sub/.dub rm -f main/dub.selections.json -${DUB} build --bare --compiler=${DC} main || exit 1 +${DUB} build --bare --compiler=${DC} main diff --git a/test/issue813-pure-sub-dependency/main/src/app.d b/test/issue813-pure-sub-dependency/main/src/app.d index dde0c1e..0b416f0 100644 --- a/test/issue813-pure-sub-dependency/main/src/app.d +++ b/test/issue813-pure-sub-dependency/main/src/app.d @@ -3,4 +3,4 @@ void main() { foo(); -} +} diff --git a/test/issue820-extra-fields-after-convert.sh b/test/issue820-extra-fields-after-convert.sh index ec9e935..5e81e35 100755 --- a/test/issue820-extra-fields-after-convert.sh +++ b/test/issue820-extra-fields-after-convert.sh @@ -1,17 +1,16 @@ -#!/bin/sh +#!/usr/bin/env bash -set -e +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/1-exec-simple cp dub.json dub.json.bak ${DUB} convert -f sdl -if grep -c -e "version\|sourcePaths\|importPaths\|configuration" dub.sdl > /dev/null; then - echo "Conversion added extra fields." +if grep -qe "version\|sourcePaths\|importPaths\|configuration" dub.sdl > /dev/null; then mv dub.json.bak dub.json rm dub.sdl - exit 1 + die $LINENO 'Conversion added extra fields.' fi mv dub.json.bak dub.json diff --git a/test/issue838-custom-cache-paths.sh b/test/issue838-custom-cache-paths.sh new file mode 100755 index 0000000..da8e84a --- /dev/null +++ b/test/issue838-custom-cache-paths.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +CONFIG_FILE=$CURR_DIR/../etc/dub/settings.json + +mkdir $CURR_DIR/../etc && mkdir $CURR_DIR/../etc/dub || true +echo "{\"customCachePaths\": [\"$CURR_DIR/issue838-custom-cache-paths/cache\"]}" > $CONFIG_FILE + +trap "rm $CONFIG_FILE" EXIT + +if ! { $DUB build --root "$CURR_DIR/issue838-custom-cache-paths" --skip-registry=all; }; then + die $LINENO 'Failed to build package with custom cache path for dependencies.' +fi diff --git a/test/issue838-custom-cache-paths/.no_build b/test/issue838-custom-cache-paths/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue838-custom-cache-paths/.no_build diff --git a/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl b/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl new file mode 100644 index 0000000..650050d --- /dev/null +++ b/test/issue838-custom-cache-paths/cache/foo-1.0.0/foo/dub.sdl @@ -0,0 +1,3 @@ +name "foo" +version "1.0.0" +targetType "sourceLibrary" diff --git a/test/issue838-custom-cache-paths/dub.sdl b/test/issue838-custom-cache-paths/dub.sdl new file mode 100644 index 0000000..f4135fc --- /dev/null +++ b/test/issue838-custom-cache-paths/dub.sdl @@ -0,0 +1,2 @@ +name "test" +dependency "foo" version="1.0.0" diff --git a/test/issue838-custom-cache-paths/source/app.d b/test/issue838-custom-cache-paths/source/app.d new file mode 100644 index 0000000..9198103 --- /dev/null +++ b/test/issue838-custom-cache-paths/source/app.d @@ -0,0 +1,3 @@ +void main() +{ +} diff --git a/test/issue877-auto-fetch-package-on-run.sh b/test/issue877-auto-fetch-package-on-run.sh new file mode 100755 index 0000000..e46146e --- /dev/null +++ b/test/issue877-auto-fetch-package-on-run.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -eu -o pipefail +set -x +$DUB remove --version="*" gitcompatibledubpackage || true + +# check whether the interactive run mode works +echo "y" | $DUB run gitcompatibledubpackage | grep "Hello DUB" +$DUB remove gitcompatibledubpackage + +! (echo "n" | $DUB run gitcompatibledubpackage | grep "Hello DUB") +! $DUB remove gitcompatibledubpackage + +# check -y +$DUB run --yes gitcompatibledubpackage | grep "Hello DUB" +$DUB remove gitcompatibledubpackage + +# check --yes +$DUB run -y gitcompatibledubpackage | grep "Hello DUB" +$DUB remove gitcompatibledubpackage + +(! $DUB run --non-interactive gitcompatibledubpackage) 2>&1 | grep "Failed to find.*gitcompatibledubpackage.*locally" + +# check supplying versions directly +dub_log="$($DUB run gitcompatibledubpackage@1.0.3)" +echo "$dub_log" | grep "Hello DUB" +echo "$dub_log" | grep "Fetching.*1.0.3" +$DUB remove gitcompatibledubpackage + +# check supplying an invalid version +(! $DUB run gitcompatibledubpackage@0.42.43) 2>&1 | \ + grep 'No package gitcompatibledubpackage was found matching the dependency 0[.]42[.]43' + +! $DUB remove gitcompatibledubpackage diff --git a/test/issue884-init-defer-file-creation.sh b/test/issue884-init-defer-file-creation.sh index 38c393e..7ffab21 100755 --- a/test/issue884-init-defer-file-creation.sh +++ b/test/issue884-init-defer-file-creation.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -e +. $(dirname "${BASH_SOURCE[0]}")/common.sh TMPDIR=${CURR_DIR}tmppack echo $TMPDIR @@ -9,9 +9,11 @@ cd ${TMPDIR} # kill dub init during interactive mode -${DUB} init < /dev/stdin & +mkfifo in +${DUB} init < in & sleep 1 kill $! +rm in # ensure that no files are left behind NFILES_PLUS_ONE=`ls -la | wc -l` @@ -21,5 +23,5 @@ # ignore sum + "." + ".." if [ ${NFILES_PLUS_ONE} -gt 3 ]; then - exit 1; + die $LINENO 'Aborted dub init left spurious files around.' fi diff --git a/test/issue895-local-configuration.sh b/test/issue895-local-configuration.sh index 8edc10f..9799288 100755 --- a/test/issue895-local-configuration.sh +++ b/test/issue895-local-configuration.sh @@ -1,25 +1,76 @@ #!/usr/bin/env bash -set -e - -cd ${CURR_DIR} -mkdir ../etc -mkdir ../etc/dub -echo "{\"defaultCompiler\": \"foo\"}" > ../etc/dub/settings.json +. $(dirname "${BASH_SOURCE[0]}")/common.sh if [ -e /var/lib/dub/settings.json ]; then - echo "Found existing system wide DUB configuration. Aborting." - exit 1 + die $LINENO 'Found existing system wide DUB configuration. Aborting.' fi if [ -e ~/.dub/settings.json ]; then - echo "Found existing user wide DUB configuration. Aborting." - exit 1 + die $LINENO 'Found existing user wide DUB configuration. Aborting.' fi -if ! ${DUB} describe --single issue103-single-file-package.d 2>&1 | grep -e "Unknown compiler: foo" -c > /dev/null; then - rm -r ../etc - echo "DUB didn't find the local configuration" - exit 1 +cd ${CURR_DIR} +mkdir -p ../etc/dub +echo "{\"defaultCompiler\": \"foo\"}" > ../etc/dub/settings.json +echo "Empty file named foo." > ../bin/foo + +function cleanup { + rm -r ../etc +} + +trap cleanup EXIT + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Unknown compiler: $(dirname $CURR_DIR)/bin/foo"; then + rm ../bin/foo + die $LINENO 'DUB did not find the local configuration with an adjacent compiler.' fi -rm -r ../etc +echo "{\"defaultCompiler\": \"$CURR_DIR/foo\"}" > ../etc/dub/settings.json +mv ../bin/foo $CURR_DIR + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Unknown compiler: $CURR_DIR/foo"; then + rm $CURR_DIR/foo + die $LINENO 'DUB did not find a locally-configured compiler with an absolute path.' +fi + +echo "{\"defaultCompiler\": \"~/.dub/foo\"}" > ../etc/dub/settings.json +mv $CURR_DIR/foo ~/.dub/ + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Unknown compiler: "; then + rm ~/.dub/foo + die $LINENO 'DUB did not find a locally-configured compiler with a tilde-prefixed path.' +fi + +echo "{\"defaultCompiler\": \"\$DUB_BINARY_PATH/../foo\"}" > ../etc/dub/settings.json +mv ~/.dub/foo .. + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Unknown compiler: $(dirname $CURR_DIR)/bin/../foo"; then + rm ../foo + die $LINENO 'DUB did not find a locally-configured compiler with a DUB-relative path.' +fi + +echo "{\"defaultCompiler\": \"../foo\"}" > ../etc/dub/settings.json + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "defaultCompiler specified in a DUB config file cannot use an unqualified relative path"; then + rm ../foo + die $LINENO 'DUB did not error properly for a locally-configured compiler with a relative path.' +fi + +rm ../etc/dub/settings.json +echo "Empty file named ldc2." > ../bin/ldc2 + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Failed to invoke the compiler $(dirname $CURR_DIR)/bin/ldc2 to determine the build platform"; then + rm ../bin/ldc2 + die $LINENO 'DUB did not find ldc2 adjacent to it.' +fi + +echo "{\"defaultCompiler\": \"foo\"}" > ../etc/dub/settings.json +rm ../bin/ldc2 +export PATH=$(dirname $CURR_DIR)${PATH:+:$PATH} + +if ! { ${DUB} describe --single issue103-single-file-package.d 2>&1 || true; } | grep -cF "Unknown compiler: foo"; then + rm ../foo + die $LINENO 'DUB did not find a locally-configured compiler in its PATH.' +fi + +rm ../foo diff --git a/test/issue923-subpackage-deps.sh b/test/issue923-subpackage-deps.sh index f35d58c..f3be79c 100755 --- a/test/issue923-subpackage-deps.sh +++ b/test/issue923-subpackage-deps.sh @@ -1,14 +1,14 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue923-subpackage-deps rm -rf main/.dub rm -rf a/.dub rm -rf b/.dub rm -f main/dub.selections.json -${DUB} build --bare --compiler=${DC} main || exit 1 +${DUB} build --bare --compiler=${DC} main if ! grep -c -e \"b\" main/dub.selections.json; then - echo "Dependency b not resolved." - exit 1 + die $LINENO 'Dependency b not resolved.' fi diff --git a/test/issue934-path-dep.sh b/test/issue934-path-dep.sh index 6aa477a..387521b 100755 --- a/test/issue934-path-dep.sh +++ b/test/issue934-path-dep.sh @@ -1,9 +1,10 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue934-path-dep rm -rf main/.dub rm -rf a/.dub rm -rf b/.dub rm -f main/dub.selections.json cd main -${DUB} build --compiler=${DC} || exit 1 +${DUB} build --compiler=${DC} diff --git a/test/issue97-targettype-none-nodeps/.fail_build b/test/issue97-targettype-none-nodeps/.fail_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue97-targettype-none-nodeps/.fail_build diff --git a/test/issue97-targettype-none-nodeps/.gitignore b/test/issue97-targettype-none-nodeps/.gitignore new file mode 100644 index 0000000..433d266 --- /dev/null +++ b/test/issue97-targettype-none-nodeps/.gitignore @@ -0,0 +1,5 @@ +.dub +docs.json +__dummy.html +*.o +*.obj diff --git a/test/issue97-targettype-none-nodeps/.no_run b/test/issue97-targettype-none-nodeps/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue97-targettype-none-nodeps/.no_run diff --git a/test/issue97-targettype-none-nodeps/a/dub.sdl b/test/issue97-targettype-none-nodeps/a/dub.sdl new file mode 100644 index 0000000..0c83ba9 --- /dev/null +++ b/test/issue97-targettype-none-nodeps/a/dub.sdl @@ -0,0 +1,2 @@ +name "a" +targetType "executable" diff --git a/test/issue97-targettype-none-nodeps/a/source/app.d b/test/issue97-targettype-none-nodeps/a/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none-nodeps/a/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none-nodeps/b/dub.sdl b/test/issue97-targettype-none-nodeps/b/dub.sdl new file mode 100644 index 0000000..97d00a8 --- /dev/null +++ b/test/issue97-targettype-none-nodeps/b/dub.sdl @@ -0,0 +1,2 @@ +name "b" +targetType "executable" diff --git a/test/issue97-targettype-none-nodeps/b/source/app.d b/test/issue97-targettype-none-nodeps/b/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none-nodeps/b/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none-nodeps/dub.sdl b/test/issue97-targettype-none-nodeps/dub.sdl new file mode 100644 index 0000000..75bd0bd --- /dev/null +++ b/test/issue97-targettype-none-nodeps/dub.sdl @@ -0,0 +1,4 @@ +name "issue97-targettype-none" +targetType "none" +subPackage "./a/" +subPackage "./b/" diff --git a/test/issue97-targettype-none-onerecipe/.gitignore b/test/issue97-targettype-none-onerecipe/.gitignore new file mode 100644 index 0000000..433d266 --- /dev/null +++ b/test/issue97-targettype-none-onerecipe/.gitignore @@ -0,0 +1,5 @@ +.dub +docs.json +__dummy.html +*.o +*.obj diff --git a/test/issue97-targettype-none-onerecipe/.no_run b/test/issue97-targettype-none-onerecipe/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue97-targettype-none-onerecipe/.no_run diff --git a/test/issue97-targettype-none-onerecipe/a/source/app.d b/test/issue97-targettype-none-onerecipe/a/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none-onerecipe/a/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none-onerecipe/b/source/app.d b/test/issue97-targettype-none-onerecipe/b/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none-onerecipe/b/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none-onerecipe/dub.sdl b/test/issue97-targettype-none-onerecipe/dub.sdl new file mode 100644 index 0000000..0914715 --- /dev/null +++ b/test/issue97-targettype-none-onerecipe/dub.sdl @@ -0,0 +1,14 @@ +name "issue97-targettype-none" +targetType "none" +dependency "issue97-targettype-none:a" version="*" +dependency "issue97-targettype-none:b" version="*" +subPackage { + name "a" + targetType "executable" + sourcePaths "a/source" +} +subPackage { + name "b" + targetType "executable" + sourcePaths "b/source" +} diff --git a/test/issue97-targettype-none.sh b/test/issue97-targettype-none.sh new file mode 100755 index 0000000..3ffe303 --- /dev/null +++ b/test/issue97-targettype-none.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +${DUB} build --root ${CURR_DIR}/issue97-targettype-none 2>&1 || true + +# make sure both sub-packages are cleaned +OUTPUT=`${DUB} clean --root ${CURR_DIR}/issue97-targettype-none 2>&1` +echo $OUTPUT | grep -c "Cleaning package at .*/issue97-targettype-none/a/" > /dev/null +echo $OUTPUT | grep -c "Cleaning package at .*/issue97-targettype-none/b/" > /dev/null diff --git a/test/issue97-targettype-none/.no_build b/test/issue97-targettype-none/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue97-targettype-none/.no_build diff --git a/test/issue97-targettype-none/.no_run b/test/issue97-targettype-none/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/issue97-targettype-none/.no_run diff --git a/test/issue97-targettype-none/a/dub.sdl b/test/issue97-targettype-none/a/dub.sdl new file mode 100644 index 0000000..0c83ba9 --- /dev/null +++ b/test/issue97-targettype-none/a/dub.sdl @@ -0,0 +1,2 @@ +name "a" +targetType "executable" diff --git a/test/issue97-targettype-none/a/source/app.d b/test/issue97-targettype-none/a/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none/a/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none/b/dub.sdl b/test/issue97-targettype-none/b/dub.sdl new file mode 100644 index 0000000..97d00a8 --- /dev/null +++ b/test/issue97-targettype-none/b/dub.sdl @@ -0,0 +1,2 @@ +name "b" +targetType "executable" diff --git a/test/issue97-targettype-none/b/source/app.d b/test/issue97-targettype-none/b/source/app.d new file mode 100644 index 0000000..c3eec7f --- /dev/null +++ b/test/issue97-targettype-none/b/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln("Edit source/app.d to start your project."); +} diff --git a/test/issue97-targettype-none/dub.sdl b/test/issue97-targettype-none/dub.sdl new file mode 100644 index 0000000..1f47423 --- /dev/null +++ b/test/issue97-targettype-none/dub.sdl @@ -0,0 +1,6 @@ +name "issue97-targettype-none" +targetType "none" +dependency "issue97-targettype-none:a" version="*" +dependency "issue97-targettype-none:b" version="*" +subPackage "./a/" +subPackage "./b/" diff --git a/test/issue990-download-optional-selected.sh b/test/issue990-download-optional-selected.sh index 1d5355c..8b548a3 100755 --- a/test/issue990-download-optional-selected.sh +++ b/test/issue990-download-optional-selected.sh @@ -1,6 +1,7 @@ -#!/bin/sh +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh cd ${CURR_DIR}/issue990-download-optional-selected rm -rf b/.dub -${DUB} remove gitcompatibledubpackage -n --version=* -${DUB} run || exit 1 +${DUB} remove gitcompatibledubpackage -n --version=* 2>/dev/null || true +${DUB} run diff --git a/test/pr1549-dub-exe-var.sh b/test/pr1549-dub-exe-var.sh new file mode 100755 index 0000000..7ad9f55 --- /dev/null +++ b/test/pr1549-dub-exe-var.sh @@ -0,0 +1,11 @@ +#! /usr/bin/env bash +set -e + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +PR1549=$CURR_DIR/pr1549-dub-exe-var + +${DUB} build --root ${PR1549} +OUTPUT=$(${PR1549}/test-application) + +if [[ "$OUTPUT" != "modified code" ]]; then die "\$DUB build variable was (likely) not evaluated correctly"; fi diff --git a/test/pr1549-dub-exe-var/.gitignore b/test/pr1549-dub-exe-var/.gitignore new file mode 100644 index 0000000..964cd1d --- /dev/null +++ b/test/pr1549-dub-exe-var/.gitignore @@ -0,0 +1,2 @@ +setmsg +setmsg.exe diff --git a/test/pr1549-dub-exe-var/.no_build b/test/pr1549-dub-exe-var/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/pr1549-dub-exe-var/.no_build diff --git a/test/pr1549-dub-exe-var/dub.sdl b/test/pr1549-dub-exe-var/dub.sdl new file mode 100644 index 0000000..fdb283a --- /dev/null +++ b/test/pr1549-dub-exe-var/dub.sdl @@ -0,0 +1,4 @@ +name "test-application" +targetType "executable" +preBuildCommands "$DUB run --single $PACKAGE_DIR/setmsg.d -- \"modified code\"" +postBuildCommands "$DUB run --single $PACKAGE_DIR/setmsg.d -- \"unmodified code\"" diff --git a/test/pr1549-dub-exe-var/setmsg.d b/test/pr1549-dub-exe-var/setmsg.d new file mode 100644 index 0000000..6eb7f93 --- /dev/null +++ b/test/pr1549-dub-exe-var/setmsg.d @@ -0,0 +1,21 @@ +/+ dub.sdl: ++/ + +import std.exception; +import std.path; +import std.process; +import std.stdio; + +void main(in string[] args) +{ + enforce(args.length > 1); + const string msg = args[1]; + + const path = buildPath(environment["DUB_PACKAGE_DIR"], "source", "app.d"); + auto file = File(path, "w"); + file.writeln(`import std.stdio;`); + file.writeln(); + file.writeln(`void main() {`); + file.writefln(` writeln("%s");`, msg); + file.writeln(`}`); +} diff --git a/test/pr1549-dub-exe-var/source/app.d b/test/pr1549-dub-exe-var/source/app.d new file mode 100644 index 0000000..e143585 --- /dev/null +++ b/test/pr1549-dub-exe-var/source/app.d @@ -0,0 +1,5 @@ +import std.stdio; + +void main() { + writeln("unmodified code"); +} diff --git a/test/run-unittest.sh b/test/run-unittest.sh index b832b0f..5f27b93 100755 --- a/test/run-unittest.sh +++ b/test/run-unittest.sh @@ -1,13 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash +set -ueo pipefail -set -v +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +> $(dirname "${BASH_SOURCE[0]}")/test.log function log() { - echo -e "\033[0;33m[INFO] "$@"\033[0m" + echo -e "\033[0;33m[INFO] $@\033[0m" + echo "[INFO] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log } function logError() { - echo -e 1>&2 "\033[0;31m"$@"\033[0m" + echo -e 1>&2 "\033[0;31m[ERROR] $@\033[0m" + echo "[ERROR] $@" >> $(dirname "${BASH_SOURCE[0]}")/test.log any_errors=1 } @@ -19,28 +24,36 @@ export -f log export -f die -if [ -z ${DUB} ]; then - die 'Error: Variable $DUB must be defined to run the tests.' +if [ -z ${DUB:-} ]; then + die 'Variable $DUB must be defined to run the tests.' fi -if [ -z ${DC} ]; then +if [ -z ${DC:-} ]; then log '$DC not defined, assuming dmd...' DC=dmd fi +DC_BIN=$(basename "$DC") CURR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +FRONTEND="${FRONTEND:-}" + +if [ "$#" -gt 0 ]; then FILTER=$1; else FILTER=".*"; fi for script in $(ls $CURR_DIR/*.sh); do - if [ "$script" = "$(readlink -f ${BASH_SOURCE[0]})" ]; then continue; fi - if [ -e $script.min_frontend ] && [ ! -z "$FRONTEND" -a "$FRONTEND" \< $(cat $script.min_frontend) ]; then continue; fi + if [[ ! "$script" =~ $FILTER ]]; then continue; fi + if [ "$script" = "$(readlink -f ${BASH_SOURCE[0]})" ] || [ "$(basename $script)" = "common.sh" ]; then continue; fi + if [ -e $script.min_frontend ] && [ ! -z "$FRONTEND" ] && [ ${FRONTEND} \< $(cat $script.min_frontend) ]; then continue; fi log "Running $script..." DUB=$DUB DC=$DC CURR_DIR="$CURR_DIR" $script || logError "Script failure." done for pack in $(ls -d $CURR_DIR/*/); do + if [[ ! "$pack" =~ $FILTER ]]; then continue; fi if [ -e $pack/.min_frontend ] && [ ! -z "$FRONTEND" -a "$FRONTEND" \< $(cat $pack/.min_frontend) ]; then continue; fi + # First we build the packages - if [ ! -e $pack/.no_build ]; then # For sourceLibrary + if [ ! -e $pack/.no_build ] && [ ! -e $pack/.no_build_$DC_BIN ]; then # For sourceLibrary + build=1 if [ -e $pack/.fail_build ]; then log "Building $pack, expected failure..." $DUB build --force --root=$pack --compiler=$DC 2>/dev/null && logError "Error: Failure expected, but build passed." @@ -48,19 +61,25 @@ log "Building $pack..." $DUB build --force --root=$pack --compiler=$DC || logError "Build failure." fi + else + build=0 fi - # We run the ones that are supposed to be runned - if [ ! -e $pack/.no_build ] && [ ! -e $pack/.no_run ]; then + # We run the ones that are supposed to be run + if [ $build -eq 1 ] && [ ! -e $pack/.no_run ] && [ ! -e $pack/.no_run_$DC_BIN ]; then log "Running $pack..." $DUB run --force --root=$pack --compiler=$DC || logError "Run failure." fi # Finally, the unittest part - if [ ! -e $pack/.no_build ] && [ ! -e $pack/.no_test ]; then + if [ $build -eq 1 ] && [ ! -e $pack/.no_test ] && [ ! -e $pack/.no_test_$DC_BIN ]; then log "Testing $pack..." $DUB test --force --root=$pack --compiler=$DC || logError "Test failure." fi done -exit $any_errors +echo +echo 'Testing summary:' +cat $(dirname "${BASH_SOURCE[0]}")/test.log + +exit ${any_errors:-0} diff --git a/test/single-file-sdl-default-name.d b/test/single-file-sdl-default-name.d new file mode 100644 index 0000000..e7e7bef --- /dev/null +++ b/test/single-file-sdl-default-name.d @@ -0,0 +1,10 @@ +/++dub.sdl: +dependency "sourcelib-simple" path="1-sourceLib-simple" ++/ +module single; + +void main(string[] args) +{ + import sourcelib.app; + entry(); +} diff --git a/test/single-file-sdl-default-name.sh b/test/single-file-sdl-default-name.sh new file mode 100755 index 0000000..ab2ba8b --- /dev/null +++ b/test/single-file-sdl-default-name.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +cd ${CURR_DIR} +rm -f single-file-sdl-default-name + +${DUB} run --single single-file-sdl-default-name.d --compiler=${DC} +if [ ! -f single-file-sdl-default-name ]; then + echo "Normal invocation did not produce a binary in the current directory" + exit 1 +fi +rm single-file-sdl-default-name diff --git a/test/test-version-opt.sh b/test/test-version-opt.sh index 2931868..3abf31b 100755 --- a/test/test-version-opt.sh +++ b/test/test-version-opt.sh @@ -1,3 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash +. $(dirname "${BASH_SOURCE[0]}")/common.sh $DUB --version | grep -qF 'DUB version' diff --git a/test/test_registry.d b/test/test_registry.d new file mode 100755 index 0000000..f5d14c1 --- /dev/null +++ b/test/test_registry.d @@ -0,0 +1,56 @@ +#!/usr/bin/env dub +/+dub.sdl: +dependency "vibe-d" version="~>0.8.5" +versions "VibeNoSSL" ++/ + +import vibe.d; + +/* +Provide a special API File Handler as Vibe.d's builtin serveStaticFiles +doesn't deal well with query params. +This will blindly check if the requestURI payload exists on the filesystem and if so, return the file. + +It replaces `?` with `__` for Windows compatibility. + +Params: + skip = initial part of the requestURI to skip over + folder = the base directory from which to serve API requests from +*/ +auto apiFileHandler(string skip, string folder) { + import std.functional : toDelegate; + void handler(HTTPServerRequest req, HTTPServerResponse res) { + import std.algorithm : skipOver; + import std.path : buildPath; + import std.file : exists; + // ? can't be part of path names on Windows + auto requestURI = req.requestURI.replace("?", "__"); + requestURI.skipOver(skip); + const reqFile = buildPath(folder, requestURI); + if (reqFile.exists) { + return req.sendFile(res, PosixPath(reqFile)); + } + } + return toDelegate(&handler); +} + +void main(string[] args) +{ + import std.conv; + immutable folder = readRequiredOption!string("folder", "Folder to service files from."); + immutable port = readRequiredOption!uint("port", "Port to use"); + auto router = new URLRouter; + router.get("stop", (HTTPServerRequest req, HTTPServerResponse res){ + res.writeVoidBody; + exitEventLoop(); + }); + router.get("/packages/gitcompatibledubpackage/1.0.2.zip", (req, res) { + res.writeBody("", HTTPStatus.badGateway); + }); + router.get("*", folder.serveStaticFiles); + router.get("/fallback/*", folder.serveStaticFiles(new HTTPFileServerSettings("/fallback"))); + router.get("/api/*", apiFileHandler("/", folder)); + router.get("/fallback/api/*", apiFileHandler("/fallback/", folder)); + listenHTTP(text(":", port), router); + runApplication(); +} diff --git a/test/timeout.sh b/test/timeout.sh new file mode 100755 index 0000000..3e841c9 --- /dev/null +++ b/test/timeout.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +PORT=$(($$ + 1024)) # PID + 1024 + +log ' Testing unconnectable registry' +if timeout 1s $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from unconnectable registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from unconnectable registry should fail immediately.' +fi + +log ' Testing non-responding registry' +cat | nc -l $PORT >/dev/null & +PID=$! +if timeout 10s $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from non-responding registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from non-responding registry should time-out within 8s.' +fi +kill $PID 2>/dev/null || true + +log ' Testing too slow registry' +{ + res=$(printf 'HTTP/1.1 200 OK\r +Server: dummy\r +Content-Type: application/json\r +Content-Length: 2\r +\r +{}') + for i in $(seq 0 $((${#res} - 1))); do + echo -n "${res:$i:1}" + sleep 1 + done +} | nc -l $PORT >/dev/null & +PID=$! +if timeout 10s time $DUB fetch dub --skip-registry=all --registry=http://localhost:$PORT; then + die 'Fetching from too slow registry should fail.' +elif [ $? -eq 124 ]; then + die 'Fetching from too slow registry should time-out within 8s.' +fi +kill $PID 2>/dev/null || true diff --git a/test/version-filters-diamond/.gitignore b/test/version-filters-diamond/.gitignore new file mode 100644 index 0000000..c09a597 --- /dev/null +++ b/test/version-filters-diamond/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance-diamond +issue1262-version-inheritance-diamond.so +issue1262-version-inheritance-diamond.dylib +issue1262-version-inheritance-diamond.dll +issue1262-version-inheritance-diamond.a +issue1262-version-inheritance-diamond.lib +issue1262-version-inheritance-diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-diamond/.no_build b/test/version-filters-diamond/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-diamond/.no_build diff --git a/test/version-filters-diamond/.no_run b/test/version-filters-diamond/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-diamond/.no_run diff --git a/test/version-filters-diamond/.no_test b/test/version-filters-diamond/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-diamond/.no_test diff --git a/test/version-filters-diamond/daughter/.gitignore b/test/version-filters-diamond/daughter/.gitignore new file mode 100644 index 0000000..f190acb --- /dev/null +++ b/test/version-filters-diamond/daughter/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +daughter.so +daughter.dylib +daughter.dll +daughter.a +daughter.lib +daughter-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-diamond/daughter/dub.sdl b/test/version-filters-diamond/daughter/dub.sdl new file mode 100644 index 0000000..9562cc5 --- /dev/null +++ b/test/version-filters-diamond/daughter/dub.sdl @@ -0,0 +1,6 @@ +name "daughter" +versions "Daughter" +debugVersions "dDaughter" +x:versionFilters "Daughter" "Parent" +x:debugVersionFilters "dDaughter" "dParent" +dependency "diamond" path="../diamond" diff --git a/test/version-filters-diamond/daughter/source/dummy.d b/test/version-filters-diamond/daughter/source/dummy.d new file mode 100644 index 0000000..4006f06 --- /dev/null +++ b/test/version-filters-diamond/daughter/source/dummy.d @@ -0,0 +1,15 @@ +module daughter.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); // via dependency +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); // local +version (Son) static assert(0, "Expected Son to not be set"); +version (Diamond) static assert(0, "Expected Diamond to not be set"); + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); // via dependency +debug (dDaughter) {} else static assert(0, "Expected dDaughter to be set"); // local +debug (dSon) {} else static assert(0, "Expected dSon to be set"); // via diamond dependency +debug (dDiamond) static assert(0, "Expected dDiamond to not be set"); + +version (Have_daughter) static assert(0, "Expected Have_daughter to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); +version (Have_diamond) static assert(0, "Expected Have_diamond to not be set"); diff --git a/test/version-filters-diamond/diamond/.gitignore b/test/version-filters-diamond/diamond/.gitignore new file mode 100644 index 0000000..c359a73 --- /dev/null +++ b/test/version-filters-diamond/diamond/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +diamond.so +diamond.dylib +diamond.dll +diamond.a +diamond.lib +diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-diamond/diamond/dub.sdl b/test/version-filters-diamond/diamond/dub.sdl new file mode 100644 index 0000000..afdb272 --- /dev/null +++ b/test/version-filters-diamond/diamond/dub.sdl @@ -0,0 +1,3 @@ +name "diamond" +versions "Diamond" +debugVersions "dDiamond" diff --git a/test/version-filters-diamond/diamond/source/dummy.d b/test/version-filters-diamond/diamond/source/dummy.d new file mode 100644 index 0000000..3c1fca5 --- /dev/null +++ b/test/version-filters-diamond/diamond/source/dummy.d @@ -0,0 +1,26 @@ +module diamond.dummy; + +template hasVersion(string v) +{ + mixin("version ("~v~") enum hasVersion = true; else enum hasVersion = false;"); +} + +template hasDebugVersion(string v) +{ + mixin("debug ("~v~") enum hasDebugVersion = true; else enum hasDebugVersion = false;"); +} + +// checking inference here +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +static assert(!hasVersion!"Son"); +static assert(!hasVersion!"Diamond"); + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); +static assert(!hasDebugVersion!"dDaughter"); +debug (dSon) {} else static assert(0, "Expected dSon to be set"); +static assert(!hasDebugVersion!"dDiamond"); + +static assert(!hasVersion!"Have_daughter"); +static assert(!hasVersion!"Have_son"); +static assert(!hasVersion!"Have_diamond"); diff --git a/test/version-filters-diamond/dub.sdl b/test/version-filters-diamond/dub.sdl new file mode 100644 index 0000000..51295fd --- /dev/null +++ b/test/version-filters-diamond/dub.sdl @@ -0,0 +1,7 @@ +name "version-filters-diamond" +versions "Parent" +debugVersions "dParent" +x:versionFilters "Parent" +x:debugVersionFilters "dParent" +dependency "daughter" path="daughter" +dependency "son" path="son" diff --git a/test/version-filters-diamond/son/.gitignore b/test/version-filters-diamond/son/.gitignore new file mode 100644 index 0000000..601a33b --- /dev/null +++ b/test/version-filters-diamond/son/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +son.so +son.dylib +son.dll +son.a +son.lib +son-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-diamond/son/dub.sdl b/test/version-filters-diamond/son/dub.sdl new file mode 100644 index 0000000..3e0a630 --- /dev/null +++ b/test/version-filters-diamond/son/dub.sdl @@ -0,0 +1,6 @@ +name "son" +versions "Son" +debugVersions "dSon" +x:versionFilters "Son" +x:debugVersionFilters "dSon" +dependency "diamond" path="../diamond" diff --git a/test/version-filters-diamond/son/source/dummy.d b/test/version-filters-diamond/son/source/dummy.d new file mode 100644 index 0000000..7e56da0 --- /dev/null +++ b/test/version-filters-diamond/son/source/dummy.d @@ -0,0 +1,15 @@ +module son.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); // via dependency +version (Daughter) {} else static assert(0, "Expected Daughter to not be set"); // via diamond dependency +version (Son) {} else static assert(0, "Expected Son to be set"); // local +version (Diamond) static assert(0, "Expected Diamond to not be set"); + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); // via dependency +debug (dDaughter) static assert(0, "Expected dDaughter to not be set"); // via diamond dependency +debug (dSon) {} else static assert(0, "Expected dSon to be set"); // local +debug (dDiamond) static assert(0, "Expected dDiamond to not be set"); + +version (Have_daughter) static assert(0, "Expected Have_daughter to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); +version (Have_diamond) static assert(0, "Expected Have_diamond to not be set"); diff --git a/test/version-filters-diamond/source/app.d b/test/version-filters-diamond/source/app.d new file mode 100644 index 0000000..2f19e5c --- /dev/null +++ b/test/version-filters-diamond/source/app.d @@ -0,0 +1,17 @@ +version (Parent) {} else static assert(0, "Expected Parent to be set"); // local +version (Daughter) {} else static assert(0, "Expected Daughter to not be set"); // via dependency +version (Son) {} else static assert(0, "Expected Son to not be set"); // via dependency +version (Diamond) static assert(0, "Expected Diamond to not be set"); // unused by dependencies + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); // local +debug (dDaughter) {} else static assert(0, "Expected dDaughter to be set"); // via dependency +debug (dSon) {} else static assert(0, "Expected dSon to not be set"); // via dependency +debug (dDiamond) static assert(0, "Expected dDiamond to not be set"); // unused by dependencies + +version (Have_daugther) static assert(0, "Expected Have_daugther to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); +version (Have_diamond) static assert(0, "Expected Have_diamond to not be set"); + +void main() +{ +} diff --git a/test/version-filters-none/.gitignore b/test/version-filters-none/.gitignore new file mode 100644 index 0000000..c09a597 --- /dev/null +++ b/test/version-filters-none/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance-diamond +issue1262-version-inheritance-diamond.so +issue1262-version-inheritance-diamond.dylib +issue1262-version-inheritance-diamond.dll +issue1262-version-inheritance-diamond.a +issue1262-version-inheritance-diamond.lib +issue1262-version-inheritance-diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-none/.no_build b/test/version-filters-none/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-none/.no_build diff --git a/test/version-filters-none/.no_run b/test/version-filters-none/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-none/.no_run diff --git a/test/version-filters-none/.no_test b/test/version-filters-none/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-none/.no_test diff --git a/test/version-filters-none/dub.sdl b/test/version-filters-none/dub.sdl new file mode 100644 index 0000000..5ad696c --- /dev/null +++ b/test/version-filters-none/dub.sdl @@ -0,0 +1,4 @@ +name "version-filters-none" +versions "Parent" +debugVersions "dParent" +x:versionFilters "none" diff --git a/test/version-filters-none/source/app.d b/test/version-filters-none/source/app.d new file mode 100644 index 0000000..cafe4cf --- /dev/null +++ b/test/version-filters-none/source/app.d @@ -0,0 +1,6 @@ +version (Parent) static assert(0, "Expected Parent to not be set"); +debug (dParent) static assert(0, "Expected dParent to not be set"); + +void main() +{ +} diff --git a/test/version-filters-source-dep/.gitignore b/test/version-filters-source-dep/.gitignore new file mode 100644 index 0000000..c09a597 --- /dev/null +++ b/test/version-filters-source-dep/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance-diamond +issue1262-version-inheritance-diamond.so +issue1262-version-inheritance-diamond.dylib +issue1262-version-inheritance-diamond.dll +issue1262-version-inheritance-diamond.a +issue1262-version-inheritance-diamond.lib +issue1262-version-inheritance-diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters-source-dep/.no_build b/test/version-filters-source-dep/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-source-dep/.no_build diff --git a/test/version-filters-source-dep/.no_run b/test/version-filters-source-dep/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-source-dep/.no_run diff --git a/test/version-filters-source-dep/.no_test b/test/version-filters-source-dep/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters-source-dep/.no_test diff --git a/test/version-filters-source-dep/dub.sdl b/test/version-filters-source-dep/dub.sdl new file mode 100644 index 0000000..73d4834 --- /dev/null +++ b/test/version-filters-source-dep/dub.sdl @@ -0,0 +1,5 @@ +name "version-filters-source-dep" +versions "Parent" +debugVersions "dParent" +x:versionFilters "none" +dependency "source-dep" path="source-dep" diff --git a/test/version-filters-source-dep/source-dep/dub.sdl b/test/version-filters-source-dep/source-dep/dub.sdl new file mode 100644 index 0000000..d9b7c63 --- /dev/null +++ b/test/version-filters-source-dep/source-dep/dub.sdl @@ -0,0 +1,7 @@ +name "source-dep" +versions "SourceDep" +debugVersions "dSourceDep" +# filter of sourceOnly libs are merged with dependents +x:versionFilters "SourceDep" +x:debugVersionFilters "dSourceDep" +targetType "sourceLibrary" diff --git a/test/version-filters-source-dep/source-dep/source/dummy.d b/test/version-filters-source-dep/source-dep/source/dummy.d new file mode 100644 index 0000000..daee233 --- /dev/null +++ b/test/version-filters-source-dep/source-dep/source/dummy.d @@ -0,0 +1,7 @@ +module sourcedep.dummy; + +version (Parent) static assert(0, "Expected Parent to not be set"); +version (SourceDep) {} else static assert(0, "Expected SourceDep to be set"); + +debug (dParent) static assert(0, "Expected dParent to not be set"); +debug (dSourceDep) {} else static assert(0, "Expected dSourceDep to be set"); diff --git a/test/version-filters-source-dep/source/app.d b/test/version-filters-source-dep/source/app.d new file mode 100644 index 0000000..13de8f5 --- /dev/null +++ b/test/version-filters-source-dep/source/app.d @@ -0,0 +1,9 @@ +version (Parent) static assert(0, "Expected Parent to not be set"); +version (SourceDep) {} else static assert(0, "Expected SourceDep to be set"); + +debug (dParent) static assert(0, "Expected dParent to not be set"); +debug (dSourceDep) {} else static assert(0, "Expected dSourceDep to be set"); + +void main() +{ +} diff --git a/test/version-filters.sh b/test/version-filters.sh new file mode 100755 index 0000000..e92f3fa --- /dev/null +++ b/test/version-filters.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +$DUB build --root="$CURR_DIR/version-filters" --filter-versions +$DUB build --root="$CURR_DIR/version-filters-diamond" --filter-versions +$DUB build --root="$CURR_DIR/version-filters-source-dep" --filter-versions +$DUB build --root="$CURR_DIR/version-filters-none" --filter-versions diff --git a/test/version-filters/.gitignore b/test/version-filters/.gitignore new file mode 100644 index 0000000..c09a597 --- /dev/null +++ b/test/version-filters/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +issue1262-version-inheritance-diamond +issue1262-version-inheritance-diamond.so +issue1262-version-inheritance-diamond.dylib +issue1262-version-inheritance-diamond.dll +issue1262-version-inheritance-diamond.a +issue1262-version-inheritance-diamond.lib +issue1262-version-inheritance-diamond-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters/.no_build b/test/version-filters/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters/.no_build diff --git a/test/version-filters/.no_run b/test/version-filters/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters/.no_run diff --git a/test/version-filters/.no_test b/test/version-filters/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/version-filters/.no_test diff --git a/test/version-filters/daughter/.gitignore b/test/version-filters/daughter/.gitignore new file mode 100644 index 0000000..f190acb --- /dev/null +++ b/test/version-filters/daughter/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +daughter.so +daughter.dylib +daughter.dll +daughter.a +daughter.lib +daughter-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters/daughter/dub.sdl b/test/version-filters/daughter/dub.sdl new file mode 100644 index 0000000..7eb94da --- /dev/null +++ b/test/version-filters/daughter/dub.sdl @@ -0,0 +1,5 @@ +name "daughter" +versions "Daughter" +debugVersions "dDaughter" +x:versionFilters "Daughter" "Parent" +x:debugVersionFilters "dDaughter" "dParent" diff --git a/test/version-filters/daughter/source/dummy.d b/test/version-filters/daughter/source/dummy.d new file mode 100644 index 0000000..7295801 --- /dev/null +++ b/test/version-filters/daughter/source/dummy.d @@ -0,0 +1,12 @@ +module daughter.dummy; + +version (Parent) {} else static assert(0, "Expected Parent to be set"); +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); +version (Son) static assert(0, "Expected Son to not be set"); + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); +debug (dDaughter) {} else static assert(0, "Expected dDaughter to be set"); +debug (dSon) static assert(0, "Expected dSon to not be set"); + +version (Have_daughter) static assert(0, "Expected Have_daughter to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); diff --git a/test/version-filters/dub.sdl b/test/version-filters/dub.sdl new file mode 100644 index 0000000..9f79da4 --- /dev/null +++ b/test/version-filters/dub.sdl @@ -0,0 +1,7 @@ +name "version-filters" +versions "Parent" +debugVersions "dParent" +x:versionFilters "Parent" +x:debugVersionFilters "dParent" +dependency "daughter" path="daughter" +dependency "son" path="son" diff --git a/test/version-filters/son/.gitignore b/test/version-filters/son/.gitignore new file mode 100644 index 0000000..601a33b --- /dev/null +++ b/test/version-filters/son/.gitignore @@ -0,0 +1,14 @@ +.dub +docs.json +__dummy.html +docs/ +son.so +son.dylib +son.dll +son.a +son.lib +son-test-* +*.exe +*.o +*.obj +*.lst diff --git a/test/version-filters/son/dub.sdl b/test/version-filters/son/dub.sdl new file mode 100644 index 0000000..e851dd6 --- /dev/null +++ b/test/version-filters/son/dub.sdl @@ -0,0 +1,5 @@ +name "son" +versions "Son" +debugVersions "dSon" +x:versionFilters "Son" +x:debugVersionFilters "dSon" diff --git a/test/version-filters/son/source/dummy.d b/test/version-filters/son/source/dummy.d new file mode 100644 index 0000000..0cfbcd3 --- /dev/null +++ b/test/version-filters/son/source/dummy.d @@ -0,0 +1,12 @@ +module son.dummy; + +version (Parent) static assert(0, "Expected Parent to not be set"); +version (Daughter) static assert(0, "Expected Daughter to not be set"); +version (Son) {} else static assert(0, "Expected Son to be set"); + +debug (dParent) static assert(0, "Expected dParent to not be set"); +debug (dDaughter) static assert(0, "Expected dDaughter to not be set"); +debug (dSon) {} else static assert(0, "Expected dSon to be set"); + +version (Have_daughter) static assert(0, "Expected Have_daughter to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); diff --git a/test/version-filters/source/app.d b/test/version-filters/source/app.d new file mode 100644 index 0000000..e674170 --- /dev/null +++ b/test/version-filters/source/app.d @@ -0,0 +1,14 @@ +version (Parent) {} else static assert(0, "Expected Parent to be set"); // local +version (Daughter) {} else static assert(0, "Expected Daughter to be set"); // via dependency +version (Son) {} else static assert(0, "Expected Son to be set"); // via dependency + +debug (dParent) {} else static assert(0, "Expected dParent to be set"); // local +debug (dDaughter) {} else static assert(0, "Expected dDaughter to be set"); // via dependency +debug (dSon) {} else static assert(0, "Expected dSon to be set"); // via dependency + +version (Have_daugther) static assert(0, "Expected Have_daugther to not be set"); +version (Have_son) static assert(0, "Expected Have_son to not be set"); + +void main() +{ +} diff --git a/travis-ci.sh b/travis-ci.sh index e2af598..6297a31 100755 --- a/travis-ci.sh +++ b/travis-ci.sh @@ -2,22 +2,58 @@ set -v -e -o pipefail -if [ -z "$FRONTEND" -o "$FRONTEND" \> 2.067.z ]; then +source ~/dlang/*/activate # activate host compiler + +if [ -z "$FRONTEND" -o "$FRONTEND" \> 2.074.z ]; then vibe_ver=$(jq -r '.versions | .["vibe-d"]' < dub.selections.json) dub fetch vibe-d --version=$vibe_ver # get optional dependency dub test --compiler=${DC} -c library-nonet fi +function clean() { + # Hard reset of the DUB local folder is necessary as some tests + # currently don't properly clean themselves + rm -rf ~/.dub + git clean -dxf -- test +} + if [ "$COVERAGE" = true ]; then # library-nonet fails to build with coverage (Issue 13742) dub test --compiler=${DC} -b unittest-cov ./build.sh -cov + + # run tests with different compilers + DUB=`pwd`/bin/dub DC=${DC} test/run-unittest.sh + deactivate + clean + + export FRONTEND=2.077 + source $(~/dlang/install.sh ldc-1.7.0 --activate) + DUB=`pwd`/bin/dub DC=${DC} test/run-unittest.sh + deactivate + clean + + export FRONTEND=2.068 + source $(~/dlang/install.sh gdc-4.8.5 --activate) + DUB=`pwd`/bin/dub DC=${DC} test/run-unittest.sh + deactivate else ./build.sh + DUB=`pwd`/bin/dub DC=${DC} test/run-unittest.sh fi -DUB=`pwd`/bin/dub DC=${DC} test/run-unittest.sh if [ "$COVERAGE" = true ]; then - dub fetch doveralls - dub run doveralls --compiler=${DC} + wget https://codecov.io/bash -O codecov.sh + bash codecov.sh +fi + +# check for trailing whitespace (needs to be done only once per build) +if [ "$COVERAGE" = true ]; then + find . -type f -name '*.d' -exec grep -Hn "[[:blank:]]$" {} \; +fi + +# check that the man page generation still works (only once) +if [ "$COVERAGE" = true ]; then + source $(~/dlang/install.sh dmd --activate) + dub --single -v scripts/man/gen_man.d fi