diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..decb0cd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.{c,h,d,di,dd,json}] +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true +charset = utf-8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5e949d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.o +*.obj + +.dub/ +dub.selections.json +bin/dub + +docs.json +__dummy.html diff --git a/.travis.yml b/.travis.yml index d4d30e9..9f144c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,47 @@ # - dmd install: - # dmd - # dub - - DMD_VER=2.064.2 + # We need: + # dub: Bootstrapping + # dmd: Latest version of the frontend + # gdc: Latest (4.9.0 / FE 2.065.0 ATM) + # ldc: Latest (0.13.0 / FE 2.064.0 ATM, no support for shared lib) + + + # Install 'old' dub to bootstrap + - OLD_DUB_VER=0.9.21 + - OLD_DUB=dub-${OLD_DUB_VER}-linux-x86_64 + - wget http://code.dlang.org/files/${OLD_DUB}.tar.gz + - sudo tar -C /usr/local/bin -zxf ${OLD_DUB}.tar.gz + + + # Install DMD (latest frontend) + - DMD_VER=2.065.0 - DMD=dmd_${DMD_VER}-0_amd64.deb - - DUB_VER=0.9.21 - - DUB=dub-${DUB_VER}-linux-x86_64 - - wget http://downloads.dlang.org/releases/2013/${DMD} + - wget http://downloads.dlang.org/releases/2014/${DMD} - sudo dpkg -i ${DMD} || true - sudo apt-get -y update - sudo apt-get -fy install - sudo dpkg -i ${DMD} - - wget http://code.dlang.org/files/${DUB}.tar.gz - - sudo tar -C /usr/local/bin -zxf ${DUB}.tar.gz + + + # Get the latest GDC + - GDC_LATEST_TAR=native_2.065_gcc4.9.0_a8ad6a6678_20140615.tar.xz + - GDC_BASE_URL='http://gdcproject.org/downloads/binaries/x86_64-linux-gnu' + - wget ${GDC_BASE_URL}/${GDC_LATEST_TAR} + - sudo tar xf ${GDC_LATEST_TAR} -C /usr/local/ + - GDC_BIN=/usr/local/x86_64-gdcproject-linux-gnu/bin/gdc + + # Get the latest LDC + - LDC_VER=0.13.0 + - LDC_URL=https://github.com/ldc-developers/ldc/releases/download/v${LDC_VER}/ldc2-${LDC_VER}-linux-x86_64.tar.gz + - wget ${LDC_URL} + - sudo tar xf ldc2-${LDC_VER}-linux-x86_64.tar.gz -C /usr/local/ + - LDC_BIN=/usr/local/ldc2-${LDC_VER}-linux-x86_64/bin/ldc2 script: - - dub test -c library-nonet - - for test in `\ls -1 test/`; do (echo "[INFO] Running test $test"; cd test/$test && dub test && dub run) || break; done + - dub test --compiler=dmd -c library-nonet + - dub test --compiler=${GDC_BIN} -c library-nonet + - dub test --compiler=${LDC_BIN} -c library-nonet + - dub build + - DUB=`pwd`/bin/dub COMPILER=dmd test/run-unittest.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index a04ead0..2448035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -v0.9.22 - 2014-06- +v0.9.22 - 2014-07- -------------------- ### Features and improvements ### @@ -16,6 +16,7 @@ - All packages are automatically copied to an isolated folder where [Dustmite](https://github.com/CyberShadow/DustMite/wiki) can do its job - DUB is run in a special mode that doesn't require expensive initialization, so that it doesn't slow down the reduction process - The test condition can be a specific exit code or an output regex match on either the compiler, linker, or program run + - Added support for single file builds (by Mathias Lang aka Geod24) - [pull #364][issue364] - The special `"*"` version specification now matches any version or branch (should always be used for referencing sub packages of the same package) - Warn about using certain build options outside of build types (in addition to warning about certain `"dflags"`) - Removed explicit linking against Phobos on Linux when building using DUB (fixed building with DMD 2.065) @@ -40,7 +41,10 @@ - Replace all "package.json" files/mentions with "dub.json" and clean up white space throughout the code base (by James Clarke aka jrtc27) - [pull #337][issue337], [pull #338][issue338], [pull #339][issue339] - `dub describe` not outputs all source/import files of all configurations and platforms - [issue #185][issue185] - Added basic support for the new human readable `"systemDependencies"` field - + - Added a `--temp-build` switch to force building in a temporary folder - [issue #294][issue294] + - Using `executeShell` when invoking tools to enable more flexible use of shell features - [issue #356][issue356] + - `dub init` now creates a default `.gitignore` file + - An exit code of `-9` for a tool now triggers a short message with a possible cause (out of memory) ### Bug fixes ### @@ -67,6 +71,8 @@ - Fixed a bogus warning that the license of a sub package differs from the parent package when the sub package doesn't specify a license - Fixed building when files from "\\UNC" paths are involved - [issue #302][issue302] - Fixed up-to-date checking for embedded sub packages (by sinkuu) - [pull #336][issue336] + - Fixed outputting multiple instances of the same platform flag which broke the build for some compilers - [issue #346][issue346] + - Fixed referencing path based sub packages - [issue #347][issue347] [issue134]: https://github.com/rejectedsoftware/dub/issues/134 [issue144]: https://github.com/rejectedsoftware/dub/issues/144 @@ -85,6 +91,7 @@ [issue283]: https://github.com/rejectedsoftware/dub/issues/283 [issue284]: https://github.com/rejectedsoftware/dub/issues/284 [issue291]: https://github.com/rejectedsoftware/dub/issues/291 +[issue294]: https://github.com/rejectedsoftware/dub/issues/294 [issue296]: https://github.com/rejectedsoftware/dub/issues/296 [issue297]: https://github.com/rejectedsoftware/dub/issues/297 [issue302]: https://github.com/rejectedsoftware/dub/issues/302 @@ -100,6 +107,10 @@ [issue337]: https://github.com/rejectedsoftware/dub/issues/337 [issue338]: https://github.com/rejectedsoftware/dub/issues/338 [issue339]: https://github.com/rejectedsoftware/dub/issues/339 +[issue346]: https://github.com/rejectedsoftware/dub/issues/346 +[issue347]: https://github.com/rejectedsoftware/dub/issues/347 +[issue356]: https://github.com/rejectedsoftware/dub/issues/356 +[issue364]: https://github.com/rejectedsoftware/dub/issues/364 v0.9.21 - 2014-02-22 diff --git a/README.md b/README.md index 4cefe22..323881f 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,15 @@ Package and build manager for [D](http://dlang.org/) applications and libraries. -There is a central [package registry](https://github.com/rejectedsoftware/dub-registry/) located at . +There is a central [package registry](https://github.com/D-Programming-Language/dub-registry/) located at . -[![Build Status](https://travis-ci.org/rejectedsoftware/dub.png)](https://travis-ci.org/rejectedsoftware/dub) +[![Build Status](https://travis-ci.org/D-Programming-Language/dub.png)](https://travis-ci.org/D-Programming-Language/dub) Introduction ------------ -DUB emerged as a more general replacement for [vibe.d's](http://vibed.org/) package manager. It does not imply a dependecy to vibe.d for packages and was extended to not only directly build projects, but also to generate project files (currently [VisualD](https://github.com/rainers/visuald) and [Mono-D](http://mono-d.alexanderbothe.com/)). +DUB emerged as a more general replacement for [vibe.d's](http://vibed.org/) package manager. It does not imply a dependecy to vibe.d for packages and was extended to not only directly build projects, but also to generate project files (currently [VisualD](https://github.com/rainers/visuald)). +[Mono-D](http://mono-d.alexanderbothe.com/) also support the use of dub.json (dub's package description) as project file. The project's philosophy is to keep things as simple as possible. All that is needed to make a project a dub package is to write a short [dub.json](http://code.dlang.org/publish) file and put the source code into a `source` subfolder. It *can* then be registered on the public [package registry](http://code.dlang.org) to be made available for everyone. Any dependencies specified in `dub.json` are automatically downloaded and made available to the project during the build process. @@ -46,10 +47,8 @@ ### Arch Linux -Moritz Maxeiner has created a PKGBUILD file for Arch: - - - Latest release: - - GIT master: +Михаил Страшун (Dicebot) maintains a dub package of the latest release in `Community`, for [x86_64](https://www.archlinux.org/packages/community/x86_64/dub/) and [i686](https://www.archlinux.org/packages/community/i686/dub/). +Moritz Maxeiner has created a PKGBUILD file for GIT master: ### Debian/Ubuntu Linux @@ -63,4 +62,4 @@ 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/rejectedsoftware/dub/blob/master/source/dub/package_.d#L40). For examples on how to replicate the command line functionality, see [commandline.d](https://github.com/rejectedsoftware/dub/blob/master/source/dub/commandline.d). +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/D-Programming-Language/dub/blob/master/source/dub/package_.d#L40). For examples on how to replicate the command line functionality, see [commandline.d](https://github.com/D-Programming-Language/dub/blob/master/source/dub/commandline.d). diff --git a/build-files.txt b/build-files.txt index ab0aa1e..7c23830 100644 --- a/build-files.txt +++ b/build-files.txt @@ -25,3 +25,6 @@ source/dub/internal/vibecompat/data/utils.d source/dub/internal/vibecompat/inet/path.d source/dub/internal/vibecompat/inet/url.d +source/dub/recipe/json.d +source/dub/recipe/packagerecipe.d +source/dub/recipe/sdl.d \ No newline at end of file diff --git a/installer/rpm/dub.spec b/installer/rpm/dub.spec deleted file mode 100644 index 65f1289..0000000 --- a/installer/rpm/dub.spec +++ /dev/null @@ -1,47 +0,0 @@ -## command is: -# rpmbuild -ba dub.spec --define 'ver 0.9.21' --define 'rel 0.rc.3' -# rpm file will be in ./dub*.rpm -# if built on a i386 platform, rpm file will be in ~/rpmbuild/RPMS/i386/dub*.rpm - -Name: dub -Summary: Package manager and meta build tool for the D programming language -Vendor: rejectedsoftware e.K. -Version: %{ver} -Release: %{rel} -License: MIT -Group: Applications/Programming - -#Source: dub.tar.gz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id} -u -n) -URL: http://code.dlang.org - -BuildRequires: tar - -%description -Package Manager for the D Programming language - -%prep -#echo prep -#tar -xf %{_sourcedir}/dub.tar.gz - -%build -echo build -cd %{srcpath} && ./build.sh - -%install -echo install -rm -rf $RPM_BUILD_ROOT -mkdir -p $RPM_BUILD_ROOT%{_bindir}/ -cp %{srcpath}/bin/dub $RPM_BUILD_ROOT%{_bindir}/ - -%files -# -# list all files that need to be copied here -# - -%defattr(755,root,root,-) -/usr/bin/dub - -%clean -cp $RPM_BUILD_ROOT/../../RPMS/*/dub*.rpm . -rm -rf $RPM_BUILD_ROOT/../../RPMS/* diff --git a/installer/rpm/make_installer.sh b/installer/rpm/make_installer.sh deleted file mode 100755 index 892a650..0000000 --- a/installer/rpm/make_installer.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -set -e -cd ../../ -DUB_PATH=`pwd` -#rm -f ~/rpmbuild/SOURCES/dub.tar.gz -#tar -pczf ~/rpmbuild/SOURCES/dub.tar.gz source build-files.txt build.sh LICENSE* -cd installer/rpm/ -for i in $(git describe | tr "-" "\n"); do - if [ "$VER" == "" ]; then - VER=${i:1} - elif [ "$REL" == "" ]; then - REL=0.$i - else - REL=$REL.$i - fi -done -if [ "$REL" == "" ]; then - REL=1 -fi -ARCH=$(uname -i) -echo Building RPM FOR $VER-$REL-$ARCH -rpmbuild -ba dub.spec --define "ver $VER" --define "rel $REL" --define="srcpath $DUB_PATH" -cp ~/rpmbuild/BUILD/dub-$VER-$REL.$ARCH.rpm . diff --git a/installer/win/EnvVarUpdate.nsh b/installer/win/EnvVarUpdate.nsh deleted file mode 100644 index b67e3ba..0000000 --- a/installer/win/EnvVarUpdate.nsh +++ /dev/null @@ -1,351 +0,0 @@ -/** - * EnvVarUpdate.nsh - * : Environmental Variables: append, prepend, and remove entries - * - * WARNING: If you use StrFunc.nsh header then include it before this file - * with all required definitions. This is to avoid conflicts - * - * Usage: - * ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString" - * - * Credits: - * Version 1.0 - * * Cal Turney (turnec2) - * * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this - * function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar, - * WriteEnvStr, and un.DeleteEnvStr - * * Diego Pedroso (deguix) for StrTok - * * Kevin English (kenglish_hi) for StrContains - * * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry - * (dandaman32) for StrReplace - * - * Version 1.1 (compatibility with StrFunc.nsh) - * * techtonik - * - * http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries - * - */ - - -!ifndef ENVVARUPDATE_FUNCTION -!define ENVVARUPDATE_FUNCTION -!verbose push -!verbose 3 -!include "LogicLib.nsh" -!include "WinMessages.NSH" -!include "StrFunc.nsh" - -; ---- Fix for conflict if StrFunc.nsh is already includes in main file ----------------------- -!macro _IncludeStrFunction StrFuncName - !ifndef ${StrFuncName}_INCLUDED - ${${StrFuncName}} - !endif - !ifndef Un${StrFuncName}_INCLUDED - ${Un${StrFuncName}} - !endif - !define un.${StrFuncName} "${Un${StrFuncName}}" -!macroend - -!insertmacro _IncludeStrFunction StrTok -!insertmacro _IncludeStrFunction StrStr -!insertmacro _IncludeStrFunction StrRep - -; ---------------------------------- Macro Definitions ---------------------------------------- -!macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString - Push "${EnvVarName}" - Push "${Action}" - Push "${RegLoc}" - Push "${PathString}" - Call EnvVarUpdate - Pop "${ResultVar}" -!macroend -!define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"' - -!macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString - Push "${EnvVarName}" - Push "${Action}" - Push "${RegLoc}" - Push "${PathString}" - Call un.EnvVarUpdate - Pop "${ResultVar}" -!macroend -!define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"' -; ---------------------------------- Macro Definitions end------------------------------------- - -;----------------------------------- EnvVarUpdate start---------------------------------------- -!define hklm_all_users 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' -!define hkcu_current_user 'HKCU "Environment"' - -!macro EnvVarUpdate UN - -Function ${UN}EnvVarUpdate - - Push $0 - Exch 4 - Exch $1 - Exch 3 - Exch $2 - Exch 2 - Exch $3 - Exch - Exch $4 - Push $5 - Push $6 - Push $7 - Push $8 - Push $9 - Push $R0 - - /* After this point: - ------------------------- - $0 = ResultVar (returned) - $1 = EnvVarName (input) - $2 = Action (input) - $3 = RegLoc (input) - $4 = PathString (input) - $5 = Orig EnvVar (read from registry) - $6 = Len of $0 (temp) - $7 = tempstr1 (temp) - $8 = Entry counter (temp) - $9 = tempstr2 (temp) - $R0 = tempChar (temp) */ - - ; Step 1: Read contents of EnvVarName from RegLoc - ; - ; Check for empty EnvVarName - ${If} $1 == "" - SetErrors - DetailPrint "ERROR: EnvVarName is blank" - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - - ; Check for valid Action - ${If} $2 != "A" - ${AndIf} $2 != "P" - ${AndIf} $2 != "R" - SetErrors - DetailPrint "ERROR: Invalid Action - must be A, P, or R" - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - - ${If} $3 == HKLM - ReadRegStr $5 ${hklm_all_users} $1 ; Get EnvVarName from all users into $5 - ${ElseIf} $3 == HKCU - ReadRegStr $5 ${hkcu_current_user} $1 ; Read EnvVarName from current user into $5 - ${Else} - SetErrors - DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"' - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - - ; Check for empty PathString - ${If} $4 == "" - SetErrors - DetailPrint "ERROR: PathString is blank" - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - - ;;khc - here check if length is going to be greater then 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 - - Push $6 - Push $7 - Push $8 - StrLen $7 $4 - StrLen $6 $5 - IntOp $8 $6 + $7 - ${If} $5 == "" - ${OrIf} $8 >= ${NSIS_MAX_STRLEN} - SetErrors - DetailPrint "Current $1 length ($6) too long to modify in NSIS; set manually if needed" - Pop $8 - Pop $7 - Pop $6 - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - Pop $8 - Pop $7 - Pop $6 - ;;khc - - ; Make sure we've got some work to do - ${If} $5 == "" - ${AndIf} $2 == "R" - SetErrors - DetailPrint "$1 is empty - Nothing to remove" - Goto EnvVarUpdate_Restore_Vars - ${EndIf} - - ; Step 2: Scrub EnvVar - ; - StrCpy $0 $5 ; Copy the contents to $0 - ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or - ; after the last one are not removed here but instead in Step 3) - ${If} $0 != "" ; If EnvVar is not empty ... - ${Do} - ${${UN}StrStr} $7 $0 " ;" - ${If} $7 == "" - ${ExitDo} - ${EndIf} - ${${UN}StrRep} $0 $0 " ;" ";" ; Remove ';' - ${Loop} - ${Do} - ${${UN}StrStr} $7 $0 "; " - ${If} $7 == "" - ${ExitDo} - ${EndIf} - ${${UN}StrRep} $0 $0 "; " ";" ; Remove ';' - ${Loop} - ${Do} - ${${UN}StrStr} $7 $0 ";;" - ${If} $7 == "" - ${ExitDo} - ${EndIf} - ${${UN}StrRep} $0 $0 ";;" ";" - ${Loop} - - ; Remove a leading or trailing semicolon from EnvVar - StrCpy $7 $0 1 0 - ${If} $7 == ";" - StrCpy $0 $0 "" 1 ; Change ';' to '' - ${EndIf} - StrLen $6 $0 - IntOp $6 $6 - 1 - StrCpy $7 $0 1 $6 - ${If} $7 == ";" - StrCpy $0 $0 $6 ; Change ';' to '' - ${EndIf} - ; DetailPrint "Scrubbed $1: [$0]" ; Uncomment to debug - ${EndIf} - - /* Step 3. Remove all instances of the target path/string (even if "A" or "P") - $6 = bool flag (1 = found and removed PathString) - $7 = a string (e.g. path) delimited by semicolon(s) - $8 = entry counter starting at 0 - $9 = copy of $0 - $R0 = tempChar */ - - ${If} $5 != "" ; If EnvVar is not empty ... - StrCpy $9 $0 - StrCpy $0 "" - StrCpy $8 0 - StrCpy $6 0 - - ${Do} - ${${UN}StrTok} $7 $9 ";" $8 "0" ; $7 = next entry, $8 = entry counter - - ${If} $7 == "" ; If we've run out of entries, - ${ExitDo} ; were done - ${EndIf} ; - - ; Remove leading and trailing spaces from this entry (critical step for Action=Remove) - ${Do} - StrCpy $R0 $7 1 - ${If} $R0 != " " - ${ExitDo} - ${EndIf} - StrCpy $7 $7 "" 1 ; Remove leading space - ${Loop} - ${Do} - StrCpy $R0 $7 1 -1 - ${If} $R0 != " " - ${ExitDo} - ${EndIf} - StrCpy $7 $7 -1 ; Remove trailing space - ${Loop} - ${If} $7 == $4 ; If string matches, remove it by not appending it - StrCpy $6 1 ; Set 'found' flag - ${ElseIf} $7 != $4 ; If string does NOT match - ${AndIf} $0 == "" ; and the 1st string being added to $0, - StrCpy $0 $7 ; copy it to $0 without a prepended semicolon - ${ElseIf} $7 != $4 ; If string does NOT match - ${AndIf} $0 != "" ; and this is NOT the 1st string to be added to $0, - StrCpy $0 $0;$7 ; append path to $0 with a prepended semicolon - ${EndIf} ; - - IntOp $8 $8 + 1 ; Bump counter - ${Loop} ; Check for duplicates until we run out of paths - ${EndIf} - - ; Step 4: Perform the requested Action - ; - ${If} $2 != "R" ; If Append or Prepend - ${If} $6 == 1 ; And if we found the target - DetailPrint "Target is already present in $1. It will be removed and" - ${EndIf} - ${If} $0 == "" ; If EnvVar is (now) empty - StrCpy $0 $4 ; just copy PathString to EnvVar - ${If} $6 == 0 ; If found flag is either 0 - ${OrIf} $6 == "" ; or blank (if EnvVarName is empty) - DetailPrint "$1 was empty and has been updated with the target" - ${EndIf} - ${ElseIf} $2 == "A" ; If Append (and EnvVar is not empty), - StrCpy $0 $0;$4 ; append PathString - ${If} $6 == 1 - DetailPrint "appended to $1" - ${Else} - DetailPrint "Target was appended to $1" - ${EndIf} - ${Else} ; If Prepend (and EnvVar is not empty), - StrCpy $0 $4;$0 ; prepend PathString - ${If} $6 == 1 - DetailPrint "prepended to $1" - ${Else} - DetailPrint "Target was prepended to $1" - ${EndIf} - ${EndIf} - ${Else} ; If Action = Remove - ${If} $6 == 1 ; and we found the target - DetailPrint "Target was found and removed from $1" - ${Else} - DetailPrint "Target was NOT found in $1 (nothing to remove)" - ${EndIf} - ${If} $0 == "" - DetailPrint "$1 is now empty" - ${EndIf} - ${EndIf} - - ; Step 5: Update the registry at RegLoc with the updated EnvVar and announce the change - ; - ClearErrors - ${If} $3 == HKLM - WriteRegExpandStr ${hklm_all_users} $1 $0 ; Write it in all users section - ${ElseIf} $3 == HKCU - WriteRegExpandStr ${hkcu_current_user} $1 $0 ; Write it to current user section - ${EndIf} - - IfErrors 0 +4 - MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3" - DetailPrint "Could not write updated $1 to $3" - Goto EnvVarUpdate_Restore_Vars - - ; "Export" our change - SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=1 - - EnvVarUpdate_Restore_Vars: - ; - ; Restore the user's variables and return ResultVar - Pop $R0 - Pop $9 - Pop $8 - Pop $7 - Pop $6 - Pop $5 - Pop $4 - Pop $3 - Pop $2 - Pop $1 - Push $0 ; Push my $0 (ResultVar) - Exch - Pop $0 ; Restore his $0 - -FunctionEnd - -!macroend ; EnvVarUpdate UN -!insertmacro EnvVarUpdate "" -!insertmacro EnvVarUpdate "un." -;----------------------------------- EnvVarUpdate end---------------------------------------- - -!verbose pop -!endif diff --git a/installer/win/banner.bmp b/installer/win/banner.bmp deleted file mode 100644 index f2f4e18..0000000 --- a/installer/win/banner.bmp +++ /dev/null Binary files differ diff --git a/installer/win/header.bmp b/installer/win/header.bmp deleted file mode 100644 index 402c0da..0000000 --- a/installer/win/header.bmp +++ /dev/null Binary files differ diff --git a/installer/win/installer.nsi b/installer/win/installer.nsi deleted file mode 100644 index 6d880da..0000000 --- a/installer/win/installer.nsi +++ /dev/null @@ -1,158 +0,0 @@ -SetCompressor /SOLID lzma - -;-------------------------------------------------------- -; Defines -;-------------------------------------------------------- - -; Options -!ifndef Version - !define /ifndef Version "0.9.21" -!endif -!define DubExecPath "..\..\bin" - -;-------------------------------------------------------- -; Includes -;-------------------------------------------------------- - -!include "MUI.nsh" -!include "EnvVarUpdate.nsh" - -;-------------------------------------------------------- -; General definitions -;-------------------------------------------------------- - -; Name of the installer -Name "dub Package Manager ${Version}" - -; Name of the output file of the installer -OutFile "dub-${Version}-setup.exe" - -; Where the program will be installed -InstallDir "$PROGRAMFILES\dub" - -; Take the installation directory from the registry, if possible -InstallDirRegKey HKLM "Software\dub" "" - -; Prevent installation of a corrupt installer -CRCCheck force - -RequestExecutionLevel admin - -;-------------------------------------------------------- -; Interface settings -;-------------------------------------------------------- - -;!define MUI_ICON "installer-icon.ico" -;!define MUI_UNICON "uninstaller-icon.ico" - -;-------------------------------------------------------- -; Installer pages -;-------------------------------------------------------- - -!define MUI_WELCOMEFINISHPAGE_BITMAP "banner.bmp" -!define MUI_HEADERIMAGE -!define MUI_HEADERIMAGE_BITMAP "header.bmp" -!insertmacro MUI_PAGE_WELCOME -!insertmacro MUI_PAGE_COMPONENTS -!insertmacro MUI_PAGE_DIRECTORY -!insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_PAGE_FINISH - -!insertmacro MUI_UNPAGE_WELCOME -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES -!insertmacro MUI_UNPAGE_FINISH - -;-------------------------------------------------------- -; The languages -;-------------------------------------------------------- - -!insertmacro MUI_LANGUAGE "English" - - -;-------------------------------------------------------- -; Required section: main program files, -; registry entries, etc. -;-------------------------------------------------------- -; -Section "dub" DubFiles - - ; This section is mandatory - SectionIn RO - - SetOutPath $INSTDIR - - ; Create installation directory - CreateDirectory "$INSTDIR" - - File "${DubExecPath}\dub.exe" - File "${DubExecPath}\libcurl.dll" - File "${DubExecPath}\libeay32.dll" - File "${DubExecPath}\ssleay32.dll" - - ; Create command line batch file - FileOpen $0 "$INSTDIR\dubvars.bat" w - FileWrite $0 "@echo.$\n" - FileWrite $0 "@echo Setting up environment for using dub from %~dp0$\n" - FileWrite $0 "@set PATH=%~dp0;%PATH%$\n" - FileClose $0 - - ; Write installation dir in the registry - WriteRegStr HKLM SOFTWARE\dub "Install_Dir" "$INSTDIR" - - ; Write registry keys to make uninstall from Windows - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "DisplayName" "dub package manager" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "NoModify" 1 - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "NoRepair" 1 - WriteUninstaller "uninstall.exe" - -SectionEnd - -Section "Add to PATH" AddDubToPath - - ; Add dub directory to path (for all users) - ${EnvVarUpdate} $0 "PATH" "A" "HKLM" "$INSTDIR" - -SectionEnd - -Section /o "Start menu shortcuts" StartMenuShortcuts - CreateDirectory "$SMPROGRAMS\DUB" - - ; install dub command prompt - CreateShortCut "$SMPROGRAMS\DUB\DUB Command Prompt.lnk" '%comspec%' '/k ""$INSTDIR\dubvars.bat""' "" "" SW_SHOWNORMAL "" "Open DUB Command Prompt" - - CreateShortCut "$SMPROGRAMS\DUB\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 -SectionEnd - -;-------------------------------------------------------- -; Uninstaller -;-------------------------------------------------------- - -Section "Uninstall" - - ; Remove directories to path (for all users) - ; (if for the current user, use HKCU) - ${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" "$INSTDIR" - - ; Remove stuff from registry - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" - DeleteRegKey HKLM SOFTWARE\dub - DeleteRegKey /ifempty HKLM SOFTWARE\dub - - ; This is for deleting the remembered language of the installation - DeleteRegKey HKCU Software\dub - DeleteRegKey /ifempty HKCU Software\dub - - ; Remove the uninstaller - Delete $INSTDIR\uninstall.exe - - ; Remove shortcuts - Delete "$SMPROGRAMS\dub\dub Command Prompt.lnk" - - ; Remove used directories - RMDir /r /REBOOTOK "$INSTDIR" - RMDir /r /REBOOTOK "$SMPROGRAMS\dub" - -SectionEnd - diff --git a/installer/win/make_installer.cmd b/installer/win/make_installer.cmd deleted file mode 100644 index 943d94c..0000000 --- a/installer/win/make_installer.cmd +++ /dev/null @@ -1,3 +0,0 @@ -set GITVER=unknown -for /f %%i in ('git describe') do set GITVER=%%i -"%ProgramFiles(x86)%\NSIS\makensis.exe" "/DVersion=%GITVER:~1%" installer.nsi diff --git a/scripts/bash-completion/dub.bash b/scripts/bash-completion/dub.bash new file mode 100644 index 0000000..4af97ba --- /dev/null +++ b/scripts/bash-completion/dub.bash @@ -0,0 +1,60 @@ +# dub(1) completion -*- shell-script -*- + +_dub() +{ + local cur prev words cword split + _init_completion -s || return + + local creation_commands + creation_commands='init run build test generate describe clean dustmite' + + local management_commands + management_commands='fetch remove upgrade add-path remove-path add-local remove-local list add-override remove-override list-overrides' + + case "$prev" in + -h|--help) + return 0 + ;; + esac + + $split && return 0 + + # Use -h -v -q because lack of comma separation between -h and --help + local common_options + common_options='-h -v -q'; + + local packages + packages=$(dub list| awk '/^[[:space:]]+/ { print $1 }') + + if [[ $cword -eq 1 ]] ; then # if one argument given + if [[ "$cur" == -* ]]; then + COMPREPLY=( $( compgen -W '$common_options $( _parse_help "$1" )' -- "$cur" ) ) + else + COMPREPLY=( $( compgen -W "$creation_commands $management_commands" -- "$cur" ) ) + fi + else + local command=${words[1]}; # use $prev instead? + + local specific_options + specific_options=$( "$1" $command --help 2>/dev/null | _parse_help - ) + + case $command in + init | add-path | remove-path | add-local | remove-local | dustmite ) + COMPREPLY=( $( compgen -d -W '$common_options $specific_options' -- "$cur" ) ) + ;; + run | build | test | generate | describe | clean | upgrade | add-override | remove-override ) + COMPREPLY=( $( compgen -W '$packages $common_options $specific_options' -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W '$common_options $specific_options' -- "$cur" ) ) + ;; + esac + fi + + [[ $COMPREPLY == *= ]] && compopt -o nospace + return + + # NOTE: Disabled for now + # _filedir +} && +complete -F _dub dub diff --git a/scripts/fish-completion/dub.fish b/scripts/fish-completion/dub.fish new file mode 100644 index 0000000..139fda3 --- /dev/null +++ b/scripts/fish-completion/dub.fish @@ -0,0 +1,118 @@ +# +# Completions for the dub command +# + +# +# Subcommands +# + +# Package creation +complete -c dub -n '__fish_use_subcommand' -x -a init -d 'Initializes an empty package skeleton' +# Build, test, and run +complete -c dub -n '__fish_use_subcommand' -x -a run -d 'Builds and runs a package' +complete -c dub -n '__fish_use_subcommand' -x -a build -d 'Builds a package' +complete -c dub -n '__fish_use_subcommand' -x -a test -d 'Executes the tests of the selected package' +complete -c dub -n '__fish_use_subcommand' -x -a generate -d 'Generates project files using the specified generator' +complete -c dub -n '__fish_use_subcommand' -x -a describe -d 'Prints a JSON description of the project and its dependencies' +complete -c dub -n '__fish_use_subcommand' -x -a clean -d 'Removes intermetiate build files and cached build results' +complete -c dub -n '__fish_use_subcommand' -x -a dustmite -d 'Create reduced test cases for build errors' +# Package management +complete -c dub -n '__fish_use_subcommand' -x -a fetch -d 'Manually retrieves and caches a package' +complete -c dub -n '__fish_use_subcommand' -x -a remove -d 'Removes a cached package' +complete -c dub -n '__fish_use_subcommand' -x -a upgrade -d 'Forces an upgrade of all dependencies' +complete -c dub -n '__fish_use_subcommand' -x -a add-path -d 'Adds a default package search path' +complete -c dub -n '__fish_use_subcommand' -x -a remove-path -d 'Removes a package search path' +complete -c dub -n '__fish_use_subcommand' -x -a add-local -d 'Adds a local package directory' +complete -c dub -n '__fish_use_subcommand' -x -a remove-local -d 'Removes a local package directory' +complete -c dub -n '__fish_use_subcommand' -x -a list -d 'Prints a list of all local packages dub is aware of' +complete -c dub -n '__fish_use_subcommand' -x -a add-override -d 'Adds a new package override' +complete -c dub -n '__fish_use_subcommand' -x -a remove-override -d 'Removes an existing package override' +complete -c dub -n '__fish_use_subcommand' -x -a list-overrides -d 'Prints a list of all local package overrides' + +# +# Subcommand options +# +for cmd in run build + complete -c dub -n "contains '$cmd' (commandline -poc)" -l rdmd -d "Use rdmd" +end +for cmd in run build test + complete -c dub -n "contains '$cmd' (commandline -poc)" -s f -l force -d "Force recompilation" +end + +for cmd in run + complete -c dub -n "contains '$cmd' (commandline -poc)" -l temp-build -d "Build in temp folder" +end + +for cmd in run build test generate describe dustmite + complete -c dub -n "contains '$cmd' (commandline -poc)" -s c -l config -r -d "Build configuration" + 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)" -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 + +for cmd in run build test generate describe dustmite fetch remove upgrade + complete -c dub -n "contains '$cmd' (commandline -poc)" -l force-remove -x -d "Force deletion" +end + +for cmd in run build dustmite + complete -c dub -n "contains '$cmd' (commandline -poc)" -l combined -d "Build project in single compiler run" +end + +for cmd in run build test generate + complete -c dub -n "contains '$cmd' (commandline -poc)" -l print-builds -d "Print list of build types" +end + +for cmd in run build generate + complete -c dub -n "contains '$cmd' (commandline -poc)" -l print-configs -d "Print list of configurations" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l print-platform -d "Print build platform identifiers" +end + +for cmd in build dustmite fetch remove + complete -c dub -n "contains '$cmd' (commandline -poc)" -x -d "Package" -a '(dub list | awk \'/^[[:space:]]+/ { print $1 }\' | cut -f 3 -d " ")' +end + +for cmd in clean + complete -c dub -n "contains '$cmd' (commandline -poc)" -l all-packages -d "Clean all known packages" +end + +for cmd in dustmite + complete -c dub -n "contains '$cmd' (commandline -poc)" -l compiler-status -x -d "Expected compiler status code" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l compiler-regex -x -d "Compiler output regular expression" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l linker-status -x -d "Expected linker status code" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l linker-regex -x -d "Linker output regular expression" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l program-status -x -d "Expected program status code" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l program-regex -x -d "Program output regular expression" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l test-package -x -d "Perform a test run" +end + +for cmd in fetch remove + complete -c dub -n "contains '$cmd' (commandline -poc)" -l version -r -d "Version to use" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l system -d "Deprecated" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l local -d "Deprecated" +end + +for cmd in upgrade + complete -c dub -n "contains '$cmd' (commandline -poc)" -l prerelease -d "Use latest pre-release version" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l verify -d "Update if successful build" + complete -c dub -n "contains '$cmd' (commandline -poc)" -l missing-only -d "Update dependencies without a selected version" +end + +for cmd in add-path remove-path add-local remove-local add-override remove-override + complete -c dub -n "contains '$cmd' (commandline -poc)" -l system -d "System-wide" +end + + + +# Common options +complete -c dub -s h -l help -d "Display help" +complete -c dub -l root -r -d "Path to operate in" +complete -c dub -l registry -r -d "Use DUB registry URL" +complete -c dub -l annotate -d "Just print actions" +complete -c dub -s v -l verbose -d "Print diagnostic output" +complete -c dub -l vverbose -d "Print debug output" +complete -c dub -s q -l quiet -d "Only print warnings and errors" +complete -c dub -l vquiet -d "Print no messages" +complete -c dub -l cache -x -d "Use cache location" -a "local system user" diff --git a/scripts/rpm-package/dub.spec b/scripts/rpm-package/dub.spec new file mode 100644 index 0000000..65f1289 --- /dev/null +++ b/scripts/rpm-package/dub.spec @@ -0,0 +1,47 @@ +## command is: +# rpmbuild -ba dub.spec --define 'ver 0.9.21' --define 'rel 0.rc.3' +# rpm file will be in ./dub*.rpm +# if built on a i386 platform, rpm file will be in ~/rpmbuild/RPMS/i386/dub*.rpm + +Name: dub +Summary: Package manager and meta build tool for the D programming language +Vendor: rejectedsoftware e.K. +Version: %{ver} +Release: %{rel} +License: MIT +Group: Applications/Programming + +#Source: dub.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id} -u -n) +URL: http://code.dlang.org + +BuildRequires: tar + +%description +Package Manager for the D Programming language + +%prep +#echo prep +#tar -xf %{_sourcedir}/dub.tar.gz + +%build +echo build +cd %{srcpath} && ./build.sh + +%install +echo install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT%{_bindir}/ +cp %{srcpath}/bin/dub $RPM_BUILD_ROOT%{_bindir}/ + +%files +# +# list all files that need to be copied here +# + +%defattr(755,root,root,-) +/usr/bin/dub + +%clean +cp $RPM_BUILD_ROOT/../../RPMS/*/dub*.rpm . +rm -rf $RPM_BUILD_ROOT/../../RPMS/* diff --git a/scripts/rpm-package/make_installer.sh b/scripts/rpm-package/make_installer.sh new file mode 100644 index 0000000..892a650 --- /dev/null +++ b/scripts/rpm-package/make_installer.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -e +cd ../../ +DUB_PATH=`pwd` +#rm -f ~/rpmbuild/SOURCES/dub.tar.gz +#tar -pczf ~/rpmbuild/SOURCES/dub.tar.gz source build-files.txt build.sh LICENSE* +cd installer/rpm/ +for i in $(git describe | tr "-" "\n"); do + if [ "$VER" == "" ]; then + VER=${i:1} + elif [ "$REL" == "" ]; then + REL=0.$i + else + REL=$REL.$i + fi +done +if [ "$REL" == "" ]; then + REL=1 +fi +ARCH=$(uname -i) +echo Building RPM FOR $VER-$REL-$ARCH +rpmbuild -ba dub.spec --define "ver $VER" --define "rel $REL" --define="srcpath $DUB_PATH" +cp ~/rpmbuild/BUILD/dub-$VER-$REL.$ARCH.rpm . diff --git a/scripts/win-installer/EnvVarUpdate.nsh b/scripts/win-installer/EnvVarUpdate.nsh new file mode 100644 index 0000000..b67e3ba --- /dev/null +++ b/scripts/win-installer/EnvVarUpdate.nsh @@ -0,0 +1,351 @@ +/** + * EnvVarUpdate.nsh + * : Environmental Variables: append, prepend, and remove entries + * + * WARNING: If you use StrFunc.nsh header then include it before this file + * with all required definitions. This is to avoid conflicts + * + * Usage: + * ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString" + * + * Credits: + * Version 1.0 + * * Cal Turney (turnec2) + * * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this + * function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar, + * WriteEnvStr, and un.DeleteEnvStr + * * Diego Pedroso (deguix) for StrTok + * * Kevin English (kenglish_hi) for StrContains + * * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry + * (dandaman32) for StrReplace + * + * Version 1.1 (compatibility with StrFunc.nsh) + * * techtonik + * + * http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries + * + */ + + +!ifndef ENVVARUPDATE_FUNCTION +!define ENVVARUPDATE_FUNCTION +!verbose push +!verbose 3 +!include "LogicLib.nsh" +!include "WinMessages.NSH" +!include "StrFunc.nsh" + +; ---- Fix for conflict if StrFunc.nsh is already includes in main file ----------------------- +!macro _IncludeStrFunction StrFuncName + !ifndef ${StrFuncName}_INCLUDED + ${${StrFuncName}} + !endif + !ifndef Un${StrFuncName}_INCLUDED + ${Un${StrFuncName}} + !endif + !define un.${StrFuncName} "${Un${StrFuncName}}" +!macroend + +!insertmacro _IncludeStrFunction StrTok +!insertmacro _IncludeStrFunction StrStr +!insertmacro _IncludeStrFunction StrRep + +; ---------------------------------- Macro Definitions ---------------------------------------- +!macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString + Push "${EnvVarName}" + Push "${Action}" + Push "${RegLoc}" + Push "${PathString}" + Call EnvVarUpdate + Pop "${ResultVar}" +!macroend +!define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"' + +!macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString + Push "${EnvVarName}" + Push "${Action}" + Push "${RegLoc}" + Push "${PathString}" + Call un.EnvVarUpdate + Pop "${ResultVar}" +!macroend +!define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"' +; ---------------------------------- Macro Definitions end------------------------------------- + +;----------------------------------- EnvVarUpdate start---------------------------------------- +!define hklm_all_users 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +!define hkcu_current_user 'HKCU "Environment"' + +!macro EnvVarUpdate UN + +Function ${UN}EnvVarUpdate + + Push $0 + Exch 4 + Exch $1 + Exch 3 + Exch $2 + Exch 2 + Exch $3 + Exch + Exch $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R0 + + /* After this point: + ------------------------- + $0 = ResultVar (returned) + $1 = EnvVarName (input) + $2 = Action (input) + $3 = RegLoc (input) + $4 = PathString (input) + $5 = Orig EnvVar (read from registry) + $6 = Len of $0 (temp) + $7 = tempstr1 (temp) + $8 = Entry counter (temp) + $9 = tempstr2 (temp) + $R0 = tempChar (temp) */ + + ; Step 1: Read contents of EnvVarName from RegLoc + ; + ; Check for empty EnvVarName + ${If} $1 == "" + SetErrors + DetailPrint "ERROR: EnvVarName is blank" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Check for valid Action + ${If} $2 != "A" + ${AndIf} $2 != "P" + ${AndIf} $2 != "R" + SetErrors + DetailPrint "ERROR: Invalid Action - must be A, P, or R" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ${If} $3 == HKLM + ReadRegStr $5 ${hklm_all_users} $1 ; Get EnvVarName from all users into $5 + ${ElseIf} $3 == HKCU + ReadRegStr $5 ${hkcu_current_user} $1 ; Read EnvVarName from current user into $5 + ${Else} + SetErrors + DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"' + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Check for empty PathString + ${If} $4 == "" + SetErrors + DetailPrint "ERROR: PathString is blank" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ;;khc - here check if length is going to be greater then 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 + + Push $6 + Push $7 + Push $8 + StrLen $7 $4 + StrLen $6 $5 + IntOp $8 $6 + $7 + ${If} $5 == "" + ${OrIf} $8 >= ${NSIS_MAX_STRLEN} + SetErrors + DetailPrint "Current $1 length ($6) too long to modify in NSIS; set manually if needed" + Pop $8 + Pop $7 + Pop $6 + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + Pop $8 + Pop $7 + Pop $6 + ;;khc + + ; Make sure we've got some work to do + ${If} $5 == "" + ${AndIf} $2 == "R" + SetErrors + DetailPrint "$1 is empty - Nothing to remove" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Step 2: Scrub EnvVar + ; + StrCpy $0 $5 ; Copy the contents to $0 + ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or + ; after the last one are not removed here but instead in Step 3) + ${If} $0 != "" ; If EnvVar is not empty ... + ${Do} + ${${UN}StrStr} $7 $0 " ;" + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 " ;" ";" ; Remove ';' + ${Loop} + ${Do} + ${${UN}StrStr} $7 $0 "; " + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 "; " ";" ; Remove ';' + ${Loop} + ${Do} + ${${UN}StrStr} $7 $0 ";;" + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 ";;" ";" + ${Loop} + + ; Remove a leading or trailing semicolon from EnvVar + StrCpy $7 $0 1 0 + ${If} $7 == ";" + StrCpy $0 $0 "" 1 ; Change ';' to '' + ${EndIf} + StrLen $6 $0 + IntOp $6 $6 - 1 + StrCpy $7 $0 1 $6 + ${If} $7 == ";" + StrCpy $0 $0 $6 ; Change ';' to '' + ${EndIf} + ; DetailPrint "Scrubbed $1: [$0]" ; Uncomment to debug + ${EndIf} + + /* Step 3. Remove all instances of the target path/string (even if "A" or "P") + $6 = bool flag (1 = found and removed PathString) + $7 = a string (e.g. path) delimited by semicolon(s) + $8 = entry counter starting at 0 + $9 = copy of $0 + $R0 = tempChar */ + + ${If} $5 != "" ; If EnvVar is not empty ... + StrCpy $9 $0 + StrCpy $0 "" + StrCpy $8 0 + StrCpy $6 0 + + ${Do} + ${${UN}StrTok} $7 $9 ";" $8 "0" ; $7 = next entry, $8 = entry counter + + ${If} $7 == "" ; If we've run out of entries, + ${ExitDo} ; were done + ${EndIf} ; + + ; Remove leading and trailing spaces from this entry (critical step for Action=Remove) + ${Do} + StrCpy $R0 $7 1 + ${If} $R0 != " " + ${ExitDo} + ${EndIf} + StrCpy $7 $7 "" 1 ; Remove leading space + ${Loop} + ${Do} + StrCpy $R0 $7 1 -1 + ${If} $R0 != " " + ${ExitDo} + ${EndIf} + StrCpy $7 $7 -1 ; Remove trailing space + ${Loop} + ${If} $7 == $4 ; If string matches, remove it by not appending it + StrCpy $6 1 ; Set 'found' flag + ${ElseIf} $7 != $4 ; If string does NOT match + ${AndIf} $0 == "" ; and the 1st string being added to $0, + StrCpy $0 $7 ; copy it to $0 without a prepended semicolon + ${ElseIf} $7 != $4 ; If string does NOT match + ${AndIf} $0 != "" ; and this is NOT the 1st string to be added to $0, + StrCpy $0 $0;$7 ; append path to $0 with a prepended semicolon + ${EndIf} ; + + IntOp $8 $8 + 1 ; Bump counter + ${Loop} ; Check for duplicates until we run out of paths + ${EndIf} + + ; Step 4: Perform the requested Action + ; + ${If} $2 != "R" ; If Append or Prepend + ${If} $6 == 1 ; And if we found the target + DetailPrint "Target is already present in $1. It will be removed and" + ${EndIf} + ${If} $0 == "" ; If EnvVar is (now) empty + StrCpy $0 $4 ; just copy PathString to EnvVar + ${If} $6 == 0 ; If found flag is either 0 + ${OrIf} $6 == "" ; or blank (if EnvVarName is empty) + DetailPrint "$1 was empty and has been updated with the target" + ${EndIf} + ${ElseIf} $2 == "A" ; If Append (and EnvVar is not empty), + StrCpy $0 $0;$4 ; append PathString + ${If} $6 == 1 + DetailPrint "appended to $1" + ${Else} + DetailPrint "Target was appended to $1" + ${EndIf} + ${Else} ; If Prepend (and EnvVar is not empty), + StrCpy $0 $4;$0 ; prepend PathString + ${If} $6 == 1 + DetailPrint "prepended to $1" + ${Else} + DetailPrint "Target was prepended to $1" + ${EndIf} + ${EndIf} + ${Else} ; If Action = Remove + ${If} $6 == 1 ; and we found the target + DetailPrint "Target was found and removed from $1" + ${Else} + DetailPrint "Target was NOT found in $1 (nothing to remove)" + ${EndIf} + ${If} $0 == "" + DetailPrint "$1 is now empty" + ${EndIf} + ${EndIf} + + ; Step 5: Update the registry at RegLoc with the updated EnvVar and announce the change + ; + ClearErrors + ${If} $3 == HKLM + WriteRegExpandStr ${hklm_all_users} $1 $0 ; Write it in all users section + ${ElseIf} $3 == HKCU + WriteRegExpandStr ${hkcu_current_user} $1 $0 ; Write it to current user section + ${EndIf} + + IfErrors 0 +4 + MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3" + DetailPrint "Could not write updated $1 to $3" + Goto EnvVarUpdate_Restore_Vars + + ; "Export" our change + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=1 + + EnvVarUpdate_Restore_Vars: + ; + ; Restore the user's variables and return ResultVar + Pop $R0 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Push $0 ; Push my $0 (ResultVar) + Exch + Pop $0 ; Restore his $0 + +FunctionEnd + +!macroend ; EnvVarUpdate UN +!insertmacro EnvVarUpdate "" +!insertmacro EnvVarUpdate "un." +;----------------------------------- EnvVarUpdate end---------------------------------------- + +!verbose pop +!endif diff --git a/scripts/win-installer/banner.bmp b/scripts/win-installer/banner.bmp new file mode 100644 index 0000000..f2f4e18 --- /dev/null +++ b/scripts/win-installer/banner.bmp Binary files differ diff --git a/scripts/win-installer/header.bmp b/scripts/win-installer/header.bmp new file mode 100644 index 0000000..402c0da --- /dev/null +++ b/scripts/win-installer/header.bmp Binary files differ diff --git a/scripts/win-installer/installer.nsi b/scripts/win-installer/installer.nsi new file mode 100644 index 0000000..6d880da --- /dev/null +++ b/scripts/win-installer/installer.nsi @@ -0,0 +1,158 @@ +SetCompressor /SOLID lzma + +;-------------------------------------------------------- +; Defines +;-------------------------------------------------------- + +; Options +!ifndef Version + !define /ifndef Version "0.9.21" +!endif +!define DubExecPath "..\..\bin" + +;-------------------------------------------------------- +; Includes +;-------------------------------------------------------- + +!include "MUI.nsh" +!include "EnvVarUpdate.nsh" + +;-------------------------------------------------------- +; General definitions +;-------------------------------------------------------- + +; Name of the installer +Name "dub Package Manager ${Version}" + +; Name of the output file of the installer +OutFile "dub-${Version}-setup.exe" + +; Where the program will be installed +InstallDir "$PROGRAMFILES\dub" + +; Take the installation directory from the registry, if possible +InstallDirRegKey HKLM "Software\dub" "" + +; Prevent installation of a corrupt installer +CRCCheck force + +RequestExecutionLevel admin + +;-------------------------------------------------------- +; Interface settings +;-------------------------------------------------------- + +;!define MUI_ICON "installer-icon.ico" +;!define MUI_UNICON "uninstaller-icon.ico" + +;-------------------------------------------------------- +; Installer pages +;-------------------------------------------------------- + +!define MUI_WELCOMEFINISHPAGE_BITMAP "banner.bmp" +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_BITMAP "header.bmp" +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +;-------------------------------------------------------- +; The languages +;-------------------------------------------------------- + +!insertmacro MUI_LANGUAGE "English" + + +;-------------------------------------------------------- +; Required section: main program files, +; registry entries, etc. +;-------------------------------------------------------- +; +Section "dub" DubFiles + + ; This section is mandatory + SectionIn RO + + SetOutPath $INSTDIR + + ; Create installation directory + CreateDirectory "$INSTDIR" + + File "${DubExecPath}\dub.exe" + File "${DubExecPath}\libcurl.dll" + File "${DubExecPath}\libeay32.dll" + File "${DubExecPath}\ssleay32.dll" + + ; Create command line batch file + FileOpen $0 "$INSTDIR\dubvars.bat" w + FileWrite $0 "@echo.$\n" + FileWrite $0 "@echo Setting up environment for using dub from %~dp0$\n" + FileWrite $0 "@set PATH=%~dp0;%PATH%$\n" + FileClose $0 + + ; Write installation dir in the registry + WriteRegStr HKLM SOFTWARE\dub "Install_Dir" "$INSTDIR" + + ; Write registry keys to make uninstall from Windows + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "DisplayName" "dub package manager" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" "NoRepair" 1 + WriteUninstaller "uninstall.exe" + +SectionEnd + +Section "Add to PATH" AddDubToPath + + ; Add dub directory to path (for all users) + ${EnvVarUpdate} $0 "PATH" "A" "HKLM" "$INSTDIR" + +SectionEnd + +Section /o "Start menu shortcuts" StartMenuShortcuts + CreateDirectory "$SMPROGRAMS\DUB" + + ; install dub command prompt + CreateShortCut "$SMPROGRAMS\DUB\DUB Command Prompt.lnk" '%comspec%' '/k ""$INSTDIR\dubvars.bat""' "" "" SW_SHOWNORMAL "" "Open DUB Command Prompt" + + CreateShortCut "$SMPROGRAMS\DUB\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 +SectionEnd + +;-------------------------------------------------------- +; Uninstaller +;-------------------------------------------------------- + +Section "Uninstall" + + ; Remove directories to path (for all users) + ; (if for the current user, use HKCU) + ${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" "$INSTDIR" + + ; Remove stuff from registry + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\dub" + DeleteRegKey HKLM SOFTWARE\dub + DeleteRegKey /ifempty HKLM SOFTWARE\dub + + ; This is for deleting the remembered language of the installation + DeleteRegKey HKCU Software\dub + DeleteRegKey /ifempty HKCU Software\dub + + ; Remove the uninstaller + Delete $INSTDIR\uninstall.exe + + ; Remove shortcuts + Delete "$SMPROGRAMS\dub\dub Command Prompt.lnk" + + ; Remove used directories + RMDir /r /REBOOTOK "$INSTDIR" + RMDir /r /REBOOTOK "$SMPROGRAMS\dub" + +SectionEnd + diff --git a/scripts/win-installer/make_installer.cmd b/scripts/win-installer/make_installer.cmd new file mode 100644 index 0000000..943d94c --- /dev/null +++ b/scripts/win-installer/make_installer.cmd @@ -0,0 +1,3 @@ +set GITVER=unknown +for /f %%i in ('git describe') do set GITVER=%%i +"%ProgramFiles(x86)%\NSIS\makensis.exe" "/DVersion=%GITVER:~1%" installer.nsi diff --git a/source/dub/commandline.d b/source/dub/commandline.d index bb816cd..c6b5228 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -189,12 +189,15 @@ // make the CWD package available so that for example sub packages can reference their // parent package. - try dub.packageManager.getTemporaryPackage(Path(root_path)); + try dub.packageManager.getOrLoadPackage(Path(root_path)); catch (Exception e) { logDiagnostic("No package found in current working directory."); } } // execute the command - try return cmd.execute(dub, remaining_args, app_args); + int rc; + try { + rc = cmd.execute(dub, remaining_args, app_args); + } catch (UsageException e) { logError("%s", e.msg); logDiagnostic("Full exception: %s", e.toString().sanitize); @@ -206,6 +209,10 @@ logDiagnostic("Full exception: %s", e.toString().sanitize); return 2; } + + if (!cmd.skipDubInitialization) + dub.shutdown(); + return rc; } class CommandArgs { @@ -336,15 +343,18 @@ string m_defaultConfig; bool m_nodeps; bool m_forceRemove = false; + bool m_disableBuildOption = false; } override void prepare(scope CommandArgs args) { - 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-nobounds, unittest, profile, docs, ddox, cov, unittest-cov and custom types" - ]); + if (!m_disableBuildOption) { + 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-nobounds, unittest, profile, docs, ddox, cov, unittest-cov and custom types" + ]); + } args.getopt("c|config", &m_buildConfig, [ "Builds the specified configuration. Configurations can be defined in dub.json" ]); @@ -366,7 +376,7 @@ ]); args.getopt("build-mode", &m_buildMode, [ "Specifies the way the compiler and linker are invoked. Valid values:", - " separate (default), allAtOnce" + " separate (default), allAtOnce, singleFile" ]); } @@ -401,7 +411,7 @@ dub.upgrade(UpgradeOptions.select); // check for updates logDiagnostic("Checking for upgrades."); - dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly); + dub.upgrade(UpgradeOptions.upgrade|UpgradeOptions.printUpgradesOnly|UpgradeOptions.useCachedResult); } dub.project.validate(); @@ -430,8 +440,8 @@ if (warn_missing_package) { bool found = existsFile(dub.rootPath ~ "source/app.d"); if (!found) - foreach (f; packageInfoFilenames) - if (existsFile(dub.rootPath ~ f)) { + foreach (f; packageInfoFiles) + if (existsFile(dub.rootPath ~ f.filename)) { found = true; break; } @@ -457,6 +467,7 @@ protected { string m_generator; bool m_rdmd = false; + bool m_tempBuild = false; bool m_run = false; bool m_force = false; bool m_combined = false; @@ -538,6 +549,7 @@ gensettings.runArgs = app_args; gensettings.force = m_force; gensettings.rdmd = m_rdmd; + gensettings.tempBuild = m_tempBuild; logDiagnostic("Generating using %s", m_generator); if (m_generator == "visuald-combined") { @@ -567,6 +579,7 @@ args.getopt("rdmd", &m_rdmd, [ "Use rdmd instead of directly invoking the compiler" ]); + args.getopt("f|force", &m_force, [ "Forces a recompilation even if the target is up to date" ]); @@ -594,6 +607,10 @@ override void prepare(scope CommandArgs args) { + args.getopt("temp-build", &m_tempBuild, [ + "Builds the project in the temp folder if possible." + ]); + super.prepare(args); m_run = true; } @@ -638,6 +655,7 @@ this.acceptsAppArgs = true; m_buildType = "unittest"; + m_disableBuildOption = true; } override void prepare(scope CommandArgs args) @@ -651,6 +669,12 @@ args.getopt("f|force", &m_force, [ "Forces a recompilation even if the target is up to date" ]); + bool coverage = false; + args.getopt("coverage", &coverage, [ + "Enables code coverage statistics to be generated." + ]); + if (coverage) m_buildType = "unittest-cov"; + super.prepare(args); } @@ -704,8 +728,7 @@ string package_name; enforceUsage(free_args.length <= 1, "Expected one or zero arguments."); - if (free_args.length >= 1) package_name = free_args[1]; - + if (free_args.length >= 1) package_name = free_args[0]; setupPackage(dub, package_name); m_defaultConfig = dub.project.getDefaultConfiguration(m_buildPlatform); @@ -778,7 +801,7 @@ 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 also 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." ]; @@ -851,15 +874,15 @@ 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.", "", - "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 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.", "", - "Without specified options, placement/removal will default to a user wide shared location." + "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", "$ cd vibelog", "$ dub", - "" + "", "This will grab all needed dependencies and compile and run the application.", "", "Note: DUB does not do a system installation of packages. Packages are instead only registered within DUB's internal ecosystem. Generation of native system packages/installers may be added later as a separate feature." diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index 06d7831..dafa331 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -175,7 +175,7 @@ version (Posix) { try { - auto pkgconfig_bin = "pkg-config"; + enum pkgconfig_bin = "pkg-config"; string[] pkgconfig_libs; foreach (lib; settings.libs) if (execute([pkgconfig_bin, "--exists", "lib"~lib]).status == 0) @@ -184,7 +184,7 @@ logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.map!(l => "lib"~l).array.join(", ")); if (pkgconfig_libs.length) { - auto libflags = execute(["pkg-config", "--libs"] ~ pkgconfig_libs.map!(l => "lib"~l)().array()); + auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs.map!(l => "lib"~l)().array()); enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); foreach (f; libflags.output.split()) { if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); @@ -208,13 +208,13 @@ /// Replaces high level fields with low level fields and converts /// dmd flags to compiler-specific flags - void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all); + void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const; /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. - void extractBuildOptions(ref BuildSettings settings); + void extractBuildOptions(ref BuildSettings settings) const; /// Adds the appropriate flag to set a target path - void setTarget(ref BuildSettings settings, in BuildPlatform platform); + void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; /// Invokes the compiler using the given flags void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback); @@ -224,16 +224,23 @@ protected final void invokeTool(string[] args, void delegate(int, string) output_callback) { + import std.string; + int status; if (output_callback) { - auto result = execute(args); + auto result = executeShell(escapeShellCommand(args)); output_callback(result.status, result.output); status = result.status; } else { - auto compiler_pid = spawnProcess(args); + auto compiler_pid = spawnShell(escapeShellCommand(args)); status = compiler_pid.wait(); } - enforce(status == 0, args[0] ~ " failed with exit code "~to!string(status)); + + version (Posix) if (status == -9) { + throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory."), + args[0], status); + } + enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); } } @@ -343,7 +350,7 @@ default: return false; version (Windows) { - case ".lib", ".obj", ".res": + case ".lib", ".obj", ".res", ".def": return true; } else { case ".a", ".o", ".so", ".dylib": @@ -352,6 +359,7 @@ } } +/// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) Path generatePlatformProbeFile() { import dub.internal.vibecompat.core.file; @@ -367,90 +375,86 @@ } fil.write(q{ - import std.array; - import std.stdio; + template toString(int v) { enum toString = v.stringof; } - void main() + pragma(msg, `{`); + pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); + pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); + pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); + pragma(msg, ` "platform": [`); + pragma(msg, ` ` ~ determinePlatform()); + pragma(msg, ` ],`); + pragma(msg, ` "architecture": [`); + pragma(msg, ` ` ~ determineArchitecture()); + pragma(msg, ` ],`); + pragma(msg, `}`); + + string determinePlatform() { - writeln(`{`); - writefln(` "compiler": "%s",`, determineCompiler()); - writefln(` "frontendVersion": %s,`, __VERSION__); - writefln(` "compilerVendor": "%s",`, __VENDOR__); - writefln(` "platform": [`); - foreach (p; determinePlatform()) writefln(` "%s",`, p); - writefln(` ],`); - writefln(` "architecture": [`); - foreach (p; determineArchitecture()) writefln(` "%s",`, p); - writefln(` ],`); - writeln(`}`); + 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() + string determineArchitecture() { - 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; - } - - 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; + 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() diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index bf56aae..9f5e65d 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -67,7 +67,8 @@ } settings.addDFlags(arch_flags); - auto result = execute(compiler_binary ~ arch_flags ~ ["-quiet", "-run", fil.toNativeString()]); + auto result = executeShell(escapeShellCommand(compiler_binary ~ arch_flags ~ + ["-quiet", "-c", "-o-", fil.toNativeString()])); enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s", compiler_binary, result.output)); @@ -87,7 +88,7 @@ return build_platform; } - void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) + void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const { enforceBuildRequirements(settings); @@ -137,7 +138,7 @@ assert(fields & BuildSetting.copyFiles); } - void extractBuildOptions(ref BuildSettings settings) + void extractBuildOptions(ref BuildSettings settings) const { Appender!(string[]) newflags; next_flag: foreach (f; settings.dflags) { @@ -153,7 +154,7 @@ settings.dflags = newflags.data; } - void setTarget(ref BuildSettings settings, in BuildPlatform platform) + void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const { final switch (settings.targetType) { case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); @@ -170,8 +171,9 @@ break; } - auto tpath = Path(settings.targetPath) ~ getTargetFileName(settings, platform); - settings.addDFlags("-of"~tpath.toNativeString()); + if (tpath is null) + tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + settings.addDFlags("-of"~tpath); } void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) @@ -188,7 +190,7 @@ { import std.string; auto tpath = Path(settings.targetPath) ~ getTargetFileName(settings, platform); - auto args = [platform.compiler, "-of"~tpath.toNativeString()]; + auto args = [platform.compilerBinary, "-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 @@ -204,7 +206,7 @@ default: if (arg.startsWith("-defaultlib=")) return true; return false; - case "-g", "-gc", "-m32", "-m64", "-shared": + case "-g", "-gc", "-m32", "-m64", "-shared", "-lib": return true; } } diff --git a/source/dub/compilers/gdc.d b/source/dub/compilers/gdc.d index 56952b3..e31053f 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -35,16 +35,16 @@ tuple(BuildOptions.inline, ["-finline-functions"]), tuple(BuildOptions.noBoundsCheck, ["-fno-bounds-check"]), tuple(BuildOptions.optimize, ["-O3"]), - //tuple(BuildOptions.profile, ["-X"]), + tuple(BuildOptions.profile, ["-pg"]), tuple(BuildOptions.unittests, ["-funittest"]), tuple(BuildOptions.verbose, ["-fd-verbose"]), tuple(BuildOptions.ignoreUnknownPragmas, ["-fignore-unknown-pragmas"]), tuple(BuildOptions.syntaxOnly, ["-fsyntax-only"]), tuple(BuildOptions.warnings, ["-Wall"]), tuple(BuildOptions.warningsAsErrors, ["-Werror", "-Wall"]), - //tuple(BuildOptions.ignoreDeprecations, ["-?"]), - //tuple(BuildOptions.deprecationWarnings, ["-?"]), - //tuple(BuildOptions.deprecationErrors, ["-?"]), + tuple(BuildOptions.ignoreDeprecations, ["-Wno-deprecated"]), + tuple(BuildOptions.deprecationWarnings, ["-Wdeprecated"]), + tuple(BuildOptions.deprecationErrors, ["-Werror", "-Wdeprecated"]), tuple(BuildOptions.property, ["-fproperty"]), ]; @@ -67,12 +67,9 @@ } settings.addDFlags(arch_flags); - auto compiler_result = execute(compiler_binary ~ arch_flags ~ ["-o", (getTempDir()~"dub_platform_probe").toNativeString(), fil.toNativeString()]); - enforce(compiler_result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s", - compiler_binary, compiler_result.output)); - auto result = execute([(getTempDir()~"dub_platform_probe").toNativeString()]); - enforce(result.status == 0, format("Failed to invoke the build platform probe: %s", - result.output)); + auto result = executeShell(escapeShellCommand(compiler_binary ~ arch_flags ~ ["-c", "-o", (getTempDir()~"dub_platform_probe").toNativeString(), fil.toNativeString()])); + 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); build_platform.compilerBinary = compiler_binary; @@ -90,7 +87,7 @@ return build_platform; } - void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) + void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const { enforceBuildRequirements(settings); @@ -140,7 +137,7 @@ assert(fields & BuildSetting.copyFiles); } - void extractBuildOptions(ref BuildSettings settings) + void extractBuildOptions(ref BuildSettings settings) const { Appender!(string[]) newflags; next_flag: foreach (f; settings.dflags) { @@ -156,7 +153,7 @@ settings.dflags = newflags.data; } - void setTarget(ref BuildSettings settings, in BuildPlatform platform) + void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const { final switch (settings.targetType) { case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); @@ -172,8 +169,9 @@ break; } - auto tpath = Path(settings.targetPath) ~ getTargetFileName(settings, platform); - settings.addDFlags("-o", tpath.toNativeString()); + if (tpath is null) + tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + settings.addDFlags("-o", tpath); } void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) @@ -188,7 +186,30 @@ void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { - assert(false, "Separate linking not implemented for GDC"); + import std.string; + string[] args; + // As the user is supposed to call setTarget prior to invoke, -o target is already set. + if (settings.targetType == TargetType.staticLibrary || settings.targetType == TargetType.staticLibrary) { + auto tpath = extractTarget(settings.dflags); + assert(tpath !is null, "setTarget should be called before invoke"); + 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 + } + logDiagnostic("%s", args.join(" ")); + invokeTool(args, output_callback); + } +} + +private string extractTarget(const string[] args) { auto i = args.countUntil("-o"); return i >= 0 ? args[i+1] : null; } + +private bool isLinkageFlag(string flag) { + switch (flag) { + case "-c": + return false; + default: + return true; } } diff --git a/source/dub/compilers/ldc.d b/source/dub/compilers/ldc.d index dad1cf3..f75caa8 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -75,7 +75,7 @@ return build_platform; } - void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) + void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const { enforceBuildRequirements(settings); @@ -127,7 +127,7 @@ assert(fields & BuildSetting.copyFiles); } - void extractBuildOptions(ref BuildSettings settings) + void extractBuildOptions(ref BuildSettings settings) const { Appender!(string[]) newflags; next_flag: foreach (f; settings.dflags) { @@ -143,7 +143,7 @@ settings.dflags = newflags.data; } - void setTarget(ref BuildSettings settings, in BuildPlatform platform) + void setTarget(ref BuildSettings settings, in BuildPlatform platform, string tpath = null) const { final switch (settings.targetType) { case TargetType.autodetect: assert(false, "Invalid target type: autodetect"); @@ -159,8 +159,9 @@ break; } - auto tpath = Path(settings.targetPath) ~ getTargetFileName(settings, platform); - settings.addDFlags("-of"~tpath.toNativeString()); + if (tpath is null) + tpath = (Path(settings.targetPath) ~ getTargetFileName(settings, platform)).toNativeString(); + settings.addDFlags("-of"~tpath); } void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) @@ -175,6 +176,6 @@ void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { - assert(false, "Separate linking not implemented for GDC"); + assert(false, "Separate linking not implemented for LDC"); } } diff --git a/source/dub/dependency.d b/source/dub/dependency.d index cef6935..58622f1 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -42,8 +42,11 @@ } // A Dependency, which matches every valid version. - static @property ANY() { return Dependency(ANY_IDENT); } - static @property INVALID() { Dependency ret; ret.m_versA = Version.HEAD; ret.m_versB = Version.RELEASE; return ret; } + static @property any() { return Dependency(ANY_IDENT); } + static @property invalid() { Dependency ret; ret.m_versA = Version.HEAD; ret.m_versB = Version.RELEASE; return ret; } + + alias ANY = any; + alias INVALID = invalid; this(string ves) { @@ -97,8 +100,8 @@ m_cmpB = skipComp(v2); m_versB = Version(v2); - enforce(!m_versA.isBranch, "Partly a branch (A): %s", ves); - enforce(!m_versB.isBranch, "Partly a branch (B): %s", ves); + enforce(!m_versA.isBranch, format("Partly a branch (A): %s", ves)); + enforce(!m_versB.isBranch, format("Partly a branch (B): %s", ves)); if (m_versB < m_versA) { swap(m_versA, m_versB); @@ -302,6 +305,7 @@ d.m_cmpB = !doCmp(m_cmpB, b,b)? m_cmpB : o.m_cmpB; d.m_versB = b; d.m_optional = m_optional && o.m_optional; + if (!d.valid) return INVALID; return d; } diff --git a/source/dub/dependencyresolver.d b/source/dub/dependencyresolver.d index 4fab2c9..cd9ef17 100644 --- a/source/dub/dependencyresolver.d +++ b/source/dub/dependencyresolver.d @@ -23,11 +23,9 @@ CONFIGS configs; hash_t toHash() const nothrow @trusted { - try { - size_t ret = typeid(string).getHash(&pack); - ret ^= typeid(CONFIGS).getHash(&configs); - return ret; - } catch assert(false); + size_t ret = typeid(string).getHash(&pack); + ret ^= typeid(CONFIGS).getHash(&configs); + return ret; } bool opEqual(in ref TreeNodes other) const { return pack == other.pack && configs == other.configs; } int opCmp(in ref TreeNodes other) const { @@ -42,11 +40,9 @@ CONFIG config; hash_t toHash() const nothrow @trusted { - try { - size_t ret = typeid(string).getHash(&pack); - ret ^= typeid(CONFIG).getHash(&config); - return ret; - } catch assert(false); + size_t ret = typeid(string).getHash(&pack); + ret ^= typeid(CONFIG).getHash(&config); + return ret; } bool opEqual(in ref TreeNode other) const { return pack == other.pack && config == other.config; } int opCmp(in ref TreeNode other) const { @@ -64,6 +60,9 @@ return p[0 .. idx]; } + auto root_base_pack = rootPackage(root.pack); + + // find all possible configurations of each possible dependency size_t[string] package_indices; CONFIG[][] all_configs; bool[TreeNode] visited; @@ -80,7 +79,8 @@ pidx = *pi; configs = all_configs[*pi]; } else { - configs = getAllConfigs(basepack); + if (basepack == root_base_pack) configs = [root.config]; + else configs = getAllConfigs(basepack); all_configs ~= configs; package_indices[basepack] = pidx; } @@ -89,12 +89,18 @@ all_configs[pidx] = configs; - foreach (v; all_configs[pidx]) + foreach (v; configs) findConfigsRec(TreeNode(ch.pack, v)); } } findConfigsRec(root); + // prepend 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 (ref cfgs; all_configs) cfgs = CONFIG.invalid ~ cfgs; + logDebug("Configurations used for dependency resolution:"); foreach (n, i; package_indices) logDebug(" %s (%s): %s", n, i, all_configs[i]); @@ -109,28 +115,37 @@ import std.algorithm : max; if (parent in visited) return -1; + visited[parent] = true; sizediff_t maxcpi = -1; sizediff_t parentidx = package_indices.get(rootPackage(parent.pack), -1); + auto parentbase = rootPackage(parent.pack); + + // loop over all dependencies foreach (ch; getChildren(parent)) { auto basepack = rootPackage(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].length) { - enforce(parentidx >= 0, format("Root package %s contains reference to invalid package %s", parent.pack, ch.pack)); + enforce(parentbase != root_base_pack, format("Root package %s contains reference to invalid 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; } - enforce(parent != root, "Invalid dependecy %s referenced by the root package."); } else { auto config = all_configs[childidx][config_indices[childidx]]; auto chnode = TreeNode(ch.pack, config); - if (!matches(ch.configs, config)) { + + if (config == CONFIG.invalid || !matches(ch.configs, config)) { // if we are at the root level, we can safely skip the maxcpi computation and instead choose another childidx config - if (parent == root) return childidx; + 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); @@ -138,7 +153,9 @@ logDebug("%s (ci=%s)", error, maxcpi); } } - maxcpi = max(maxcpi, validateConfigs(chnode, error)); + + if (config != CONFIG.invalid) + maxcpi = max(maxcpi, validateConfigs(chnode, error)); } } return maxcpi; @@ -168,8 +185,10 @@ if (conflict_index < 0) { CONFIG[string] ret; foreach (p, i; package_indices) - if (all_configs[i].length) - ret[p] = all_configs[i][config_indices[i]]; + if (all_configs[i].length) { + auto cfg = all_configs[i][config_indices[i]]; + if (cfg != CONFIG.invalid) ret[p] = cfg; + } return ret; } @@ -194,44 +213,51 @@ unittest { - static class TestResolver : DependencyResolver!(uint[], uint) { + static struct IntConfig { + int value; + alias value this; + enum invalid = IntConfig(-1); + } + static IntConfig ic(int v) { return IntConfig(v); } + + static class TestResolver : DependencyResolver!(IntConfig[], IntConfig) { private TreeNodes[][string] m_children; this(TreeNodes[][string] children) { m_children = children; } - protected override uint[] getAllConfigs(string pack) { - auto ret = appender!(uint[]); + protected override IntConfig[] getAllConfigs(string pack) { + auto ret = appender!(IntConfig[]); foreach (p; m_children.byKey) { if (p.length <= pack.length+1) continue; if (p[0 .. pack.length] != pack || p[pack.length] != ':') continue; auto didx = p.lastIndexOf(':'); - ret ~= p[didx+1 .. $].to!uint; + ret ~= ic(p[didx+1 .. $].to!uint); } ret.data.sort!"a>b"(); return ret.data; } - protected override uint[] getSpecificConfigs(TreeNodes nodes) { return null; } + protected override IntConfig[] getSpecificConfigs(TreeNodes nodes) { return null; } protected override TreeNodes[] getChildren(TreeNode node) { return m_children.get(node.pack ~ ":" ~ node.config.to!string(), null); } - protected override bool matches(uint[] configs, uint config) { return configs.canFind(config); } + protected override bool matches(IntConfig[] configs, IntConfig config) { return configs.canFind(config); } } // properly back up if conflicts are detected along the way (d:2 vs d:1) with (TestResolver) { auto res = new TestResolver([ - "a:0": [TreeNodes("b", [2, 1]), TreeNodes("d", [1]), TreeNodes("e", [2, 1])], - "b:1": [TreeNodes("c", [2, 1]), TreeNodes("d", [1])], - "b:2": [TreeNodes("c", [3, 2]), TreeNodes("d", [2, 1])], + "a:0": [TreeNodes("b", [ic(2), ic(1)]), TreeNodes("d", [ic(1)]), TreeNodes("e", [ic(2), ic(1)])], + "b:1": [TreeNodes("c", [ic(2), ic(1)]), TreeNodes("d", [ic(1)])], + "b:2": [TreeNodes("c", [ic(3), ic(2)]), TreeNodes("d", [ic(2), ic(1)])], "c:1": [], "c:2": [], "c:3": [], "d:1": [], "d:2": [], "e:1": [], "e:2": [], ]); - assert(res.resolve(TreeNode("a", 0)) == ["b":2u, "c":3u, "d":1u, "e":2u], format("%s", res.resolve(TreeNode("a", 0)))); + assert(res.resolve(TreeNode("a", ic(0))) == ["b":ic(2), "c":ic(3), "d":ic(1), "e":ic(2)], format("%s", res.resolve(TreeNode("a", ic(0))))); } // handle cyclic dependencies gracefully with (TestResolver) { auto res = new TestResolver([ - "a:0": [TreeNodes("b", [1])], - "b:1": [TreeNodes("b", [1])] + "a:0": [TreeNodes("b", [ic(1)])], + "b:1": [TreeNodes("b", [ic(1)])] ]); - assert(res.resolve(TreeNode("a", 0)) == ["b":1u]); + assert(res.resolve(TreeNode("a", ic(0))) == ["b":ic(1)]); } } diff --git a/source/dub/dub.d b/source/dub/dub.d index 1fe7d4d..6941880 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -105,6 +105,10 @@ .array; ps ~= defaultPackageSuppliers(); + auto cacheDir = m_userDubPath ~ "cache/"; + foreach (p; ps) + p.loadCache(cacheDir); + m_packageSuppliers = ps; m_packageManager = new PackageManager(m_userDubPath, m_systemDubPath); updatePackageSearchPath(); @@ -118,6 +122,14 @@ updatePackageSearchPath(); } + /// Perform cleanup and persist caches to disk + void shutdown() + { + auto cacheDir = m_userDubPath ~ "cache/"; + foreach (p; m_packageSuppliers) + p.storeCache(cacheDir); + } + @property void dryRun(bool v) { m_dryRun = v; } /** Returns the root path (usually the current working directory). @@ -182,7 +194,7 @@ foreach (p; m_project.selections.selectedPackages) { auto dep = m_project.selections.getSelectedVersion(p); if (!dep.path.empty) { - if (m_packageManager.getTemporaryPackage(dep.path)) continue; + if (m_packageManager.getOrLoadPackage(dep.path)) continue; } else { if (m_packageManager.getPackage(p, dep.version_)) continue; foreach (ps; m_packageSuppliers) { @@ -202,19 +214,66 @@ } } + 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); - auto versions = resolver.resolve(m_project.rootPackage, m_project.selections); + if (options & UpgradeOptions.printUpgradesOnly) { + bool any = false; + string rootbasename = getBasePackageName(m_project.rootPackage.name); + + foreach (p, ver; versions) { + if (!ver.path.empty) continue; + + auto basename = getBasePackageName(p); + if (basename == rootbasename) continue; + + if (!m_project.selections.hasSelectedVersion(basename)) { + logInfo("Package %s can be installed with version %s.", + basename, ver); + any = true; + continue; + } + 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.", + basename, sver, ver); + any = true; + } + if (any) logInfo("Use \"dub upgrade\" to perform those changes."); + return; + } foreach (p, ver; versions) { assert(!p.canFind(":"), "Resolved packages contain a sub package!?: "~p); Package pack; - if (!ver.path.empty) pack = m_packageManager.getTemporaryPackage(ver.path); - else pack = m_packageManager.getBestPackage(p, ver); + if (!ver.path.empty) pack = m_packageManager.getOrLoadPackage(ver.path); + else { + pack = m_packageManager.getBestPackage(p, ver); + if (pack && m_packageManager.isManagedPackage(pack) + && ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0) + { + // 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); + 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); + if (!pack) fetch(p, ver, defaultPlacementLocation, fetchOpts, "getting selected version"); if ((options & UpgradeOptions.select) && ver.path.empty && p != m_project.rootPackage.name) m_project.selections.selectVersion(p, ver.version_); } @@ -356,7 +415,7 @@ void cleanPackage(Path path) { logInfo("Cleaning package at %s...", path.toNativeString()); - enforce(Package.isPackageAt(path), "No package found.", path.toNativeString()); + enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString()); // TODO: clear target files and copy files @@ -369,7 +428,7 @@ string[string] cachedPackages() const { return m_project.cachedPackagesIDs(); } /// Fetches the package matching the dependency and places it in the specified location. - Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options) + Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "") { Json pinfo; PackageSupplier supplier; @@ -413,7 +472,8 @@ } } - logInfo("Fetching %s %s...", packageId, ver); + if (reason.length) logInfo("Fetching %s %s (%s)...", packageId, ver, reason); + else logInfo("Fetching %s %s...", packageId, ver); if (m_dryRun) return null; logDiagnostic("Acquiring package zip file"); @@ -556,6 +616,7 @@ auto ddox_dub = new Dub(m_packageSuppliers); ddox_dub.loadPackage(ddox_pack.path); + ddox_dub.upgrade(UpgradeOptions.select); auto compiler_binary = "dmd"; @@ -675,6 +736,7 @@ forceRemove = 1<<3, /// Force removing package folders, which contain unknown files 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 } class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { @@ -764,8 +826,25 @@ logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config); return null; } + auto basepack = pack.basePackage; + foreach (dname, dspec; pack.dependencies) { auto dbasename = getBasePackageName(dname); + + // detect dependencies to the root package (or sub packages thereof) + if (dbasename == basepack.name) { + auto absdeppath = dspec.mapToPath(pack.path).path; + auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(dname), true); + if (subpack) { + auto desireddeppath = dname == dbasename ? basepack.path : subpack.path; + enforce(dspec.path.empty || absdeppath == desireddeppath, + format("Dependency from %s to root package references wrong path: %s vs. %s", + node.pack, absdeppath.toNativeString(), desireddeppath.toNativeString())); + } + ret ~= TreeNodes(dname, node.config); + continue; + } + if (dspec.optional && !m_dub.packageManager.getFirstPackage(dname)) continue; if (m_options & UpgradeOptions.upgrade || !m_selectedVersions || !m_selectedVersions.hasSelectedVersion(dbasename)) @@ -783,6 +862,21 @@ private Package getPackage(string name, Dependency dep) { + auto basename = getBasePackageName(name); + + // for sub packages, first try to get them from the base package + if (basename != name) { + auto subname = getSubPackageName(name); + auto basepack = getPackage(basename, dep); + if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) { + return sp; + } else if (!basepack.subPackages.canFind!(p => p.path.length)) { + // note: external sub packages are handled further below + logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_); + return null; + } + } + if (!dep.path.empty) { auto ret = m_dub.packageManager.getOrLoadPackage(dep.path); if (dep.matches(ret.ver)) return ret; @@ -792,7 +886,6 @@ return ret; auto key = name ~ ":" ~ dep.version_.toString(); - if (auto ret = key in m_remotePackages) return *ret; @@ -812,11 +905,12 @@ logDebug("Full error: %s", e.toString().sanitize); } } else { + logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, dep.version_.toString()); try { FetchOptions fetchOpts; fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none; fetchOpts |= (m_options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none; - m_dub.fetch(rootpack, dep, defaultPlacementLocation, fetchOpts); + m_dub.fetch(rootpack, dep, defaultPlacementLocation, fetchOpts, "need sub package description"); auto ret = m_dub.m_packageManager.getBestPackage(name, dep); if (!ret) { logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name); diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 00bc4e6..7a880f5 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -26,6 +26,9 @@ import std.string; import std.encoding : sanitize; +version(Windows) enum objSuffix = ".obj"; +else enum objSuffix = ".o"; + class BuildGenerator : ProjectGenerator { private { Project m_project; @@ -125,8 +128,9 @@ return true; } - if (!isWritableDir(target_path, true)) { - logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString()); + if (settings.tempBuild || !isWritableDir(target_path, true)) { + if (!settings.tempBuild) + logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString()); performDirectBuild(settings, buildsettings, pack, config); return false; } @@ -177,7 +181,7 @@ Path exe_file_path; bool tmp_target = false; if (generate_binary) { - if (settings.run && !isWritableDir(Path(buildsettings.targetPath), true)) { + if (settings.tempBuild || (settings.run && !isWritableDir(Path(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; auto tmpdir = getTempDir()~".rdmd/source/"; @@ -242,7 +246,7 @@ Path exe_file_path; bool is_temp_target = false; if (generate_binary) { - if (settings.run && !isWritableDir(Path(buildsettings.targetPath), true)) { + if (settings.tempBuild || (settings.run && !isWritableDir(Path(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)); auto tmppath = getTempDir()~("dub/"~rnd~"/"); @@ -293,7 +297,7 @@ return format("%s-%s-%s-%s-%s-%s", config, settings.buildType, settings.platform.platform.join("."), settings.platform.architecture.join("."), - settings.platform.compilerBinary, hashstr); + settings.platform.compiler, hashstr); } private void copyTargetFile(Path build_path, BuildSettings buildsettings, BuildPlatform platform) @@ -303,7 +307,7 @@ logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath); if (!existsFile(Path(buildsettings.targetPath))) mkdirRecurse(buildsettings.targetPath); - copyFile(src, Path(buildsettings.targetPath) ~ filename, true); + symlinkFile(src, Path(buildsettings.targetPath) ~ filename, true); } private bool isUpToDate(Path target_path, BuildSettings buildsettings, BuildPlatform platform, in Package main_pack, in Package[] packages, in Path[] additional_dep_files) @@ -323,7 +327,7 @@ allfiles ~= buildsettings.stringImportFiles; // TODO: add library files foreach (p; packages) - allfiles ~= (p.packageInfoFile != Path.init ? p : p.basePackage).packageInfoFile.toNativeString(); + allfiles ~= (p.packageInfoFilename != Path.init ? p : p.basePackage).packageInfoFilename.toNativeString(); foreach (f; additional_dep_files) allfiles ~= f.toNativeString(); if (main_pack is m_project.rootPackage) allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString(); @@ -340,6 +344,24 @@ return true; } + /// Output an unique name to represent the source file. + /// Calls with path that resolve to the same file on the filesystem will return the same, + /// unless they include different symbolic links (which are not resolved). + static string pathToObjName(string path) { return std.path.buildNormalizedPath(getcwd(), path~objSuffix)[1..$].replace("/", "."); } + /// 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; + string objPath = tempobj.toNativeString(); + bs.libs = null; + bs.lflags = null; + bs.addDFlags("-c"); + bs.sourceFiles = [ srcFile ]; + gs.compiler.prepareBuildSettings(bs, BuildSetting.commandLine); + gs.compiler.setTarget(bs, gs.platform, objPath); + gs.compiler.invoke(bs, gs.platform, gs.compileCallback); + return objPath; + } + void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings) { auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); @@ -352,13 +374,27 @@ if (generate_binary && existsFile(tpath)) removeFile(tpath); } + if (settings.buildMode == BuildMode.singleFile && generate_binary) { + auto lbuildsettings = buildsettings; + auto objs = appender!(string[])(); + logInfo("Compiling using %s...", settings.platform.compilerBinary); + foreach (file; buildsettings.sourceFiles.filter!(f=>!isLinkerFile(f))) { + logInfo("Compiling %s...", file); + objs.put(compileUnit(file, pathToObjName(file), buildsettings, settings)); + } + + logInfo("Linking..."); + lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f=> f.isLinkerFile()).array; + settings.compiler.setTarget(lbuildsettings, settings.platform); + settings.compiler.prepareBuildSettings(lbuildsettings, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); + settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs.data, settings.linkCallback); /* NOTE: for DMD experimental separate compile/link is used, but this is not yet implemented 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) */ - if (settings.buildMode == BuildMode.allAtOnce || settings.platform.compilerBinary != "dmd" || !generate_binary || is_static_library) { + } else if (settings.buildMode == BuildMode.allAtOnce || settings.platform.compilerBinary != "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); @@ -371,9 +407,7 @@ settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); } else { // determine path for the temporary object file - string tempobjname = buildsettings.targetName; - version(Windows) tempobjname ~= ".obj"; - else tempobjname ~= ".o"; + string tempobjname = buildsettings.targetName ~ objSuffix; Path tempobj = Path(buildsettings.targetPath) ~ tempobjname; // setup linker command line diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 081c67b..8184818 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -17,7 +17,7 @@ import dub.packagemanager; import dub.project; -import std.algorithm : map, filter, canFind; +import std.algorithm : map, filter, canFind, balancedParens; import std.array : array; import std.array; import std.exception; @@ -117,7 +117,8 @@ } // start to build up the build settings - BuildSettings buildsettings = settings.buildSettings.dup; + BuildSettings buildsettings; + if (generates_binary) buildsettings = settings.buildSettings.dup; processVars(buildsettings, m_project, pack, shallowbs, true); // remove any mainSourceFile from library builds @@ -207,7 +208,7 @@ bool combined; // compile all in one go instead of each dependency separately // only used for generator "build" - bool run, force, direct, clean, rdmd; + bool run, force, direct, clean, rdmd, tempBuild; string[] runArgs; void delegate(int status, string output) compileCallback; void delegate(int status, string output) linkCallback; @@ -221,7 +222,7 @@ enum BuildMode { separate, /// Compile and link separately allAtOnce, /// Perform compile and link with a single compiler invocation - //singleFile, /// Compile each file separately + singleFile, /// Compile each file separately //multipleObjects, /// Generate an object file per module //multipleObjectsPerModule, /// Use the -multiobj switch to generate multiple object files per module //compileOnly /// Do not invoke the linker (can be done using a post build command) @@ -277,15 +278,44 @@ mkdirRecurse(buildsettings.targetPath); if (buildsettings.copyFiles.length) { - logInfo("Copying files for %s...", pack); - foreach (f; buildsettings.copyFiles) { - auto src = Path(f); + void tryCopyFile(string file) + { + auto src = Path(file); if (!src.absolute) src = pack_path ~ src; - auto dst = target_path ~ Path(f).head; + auto dst = target_path ~ Path(file).head; logDiagnostic(" %s to %s", src.toNativeString(), dst.toNativeString()); try { copyFile(src, dst, true); - } catch logWarn("Failed to copy to %s", dst.toNativeString()); + } catch logWarn("Failed to copy %s to %s", src.toNativeString(), dst.toNativeString()); + } + logInfo("Copying files for %s...", pack); + string[] globs; + foreach (f; buildsettings.copyFiles) + { + if (f.canFind("*", "?") || + (f.canFind("{") && f.balancedParens('{', '}')) || + (f.canFind("[") && f.balancedParens('[', ']'))) + { + globs ~= f; + } + else + { + tryCopyFile(f); + } + } + if (globs.length) // Search all files for glob matches + { + foreach (f; dirEntries(pack_path.toNativeString(), SpanMode.breadth)) + { + foreach (glob; globs) + { + if (f.globMatch(glob)) + { + tryCopyFile(f); + break; + } + } + } } } } diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index 86de079..21fa661 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -184,8 +184,8 @@ } foreach (p; targets[packname].packages) - if (!p.packageInfoFile.empty) - addFile(p.packageInfoFile.toNativeString(), false); + if (!p.packageInfoFilename.empty) + addFile(p.packageInfoFilename.toNativeString(), false); if (files.targetType == TargetType.staticLibrary) foreach(s; files.sourceFiles.filter!(s => !isLinkerFile(s))) addFile(s, true); @@ -334,7 +334,7 @@ final switch (settings.buildMode) with (BuildMode) { case separate: singlefilemode = 2; break; case allAtOnce: singlefilemode = 0; break; - //case singleFile: singlefilemode = 1; break; + case singleFile: singlefilemode = 1; break; //case compileOnly: singlefilemode = 3; break; } ret.formattedWrite(" %s\n", singlefilemode); diff --git a/source/dub/init.d b/source/dub/init.d index f1121ee..a9e1ee2 100644 --- a/source/dub/init.d +++ b/source/dub/init.d @@ -9,7 +9,7 @@ import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.core.log; -import dub.package_ : packageInfoFilenames, defaultPackageFilename; +import dub.package_ : packageInfoFiles, defaultPackageFilename; import std.datetime; import std.exception; @@ -21,6 +21,10 @@ void initPackage(Path root_path, string type) { + void enforceDoesNotExist(string filename) { + enforce(!existsFile(root_path ~ filename), "The target directory already contains a '"~filename~"' file. Aborting."); + } + //Check to see if a target directory needs to be created if( !root_path.empty ){ if( !existsFile(root_path) ) @@ -28,15 +32,19 @@ } //Make sure we do not overwrite anything accidentally - auto files = packageInfoFilenames ~ ["source/", "views/", "public/"]; + foreach (fil; packageInfoFiles) + enforceDoesNotExist(fil.filename); + + auto files = ["source/", "views/", "public/", "dub.json", ".gitignore"]; foreach (fil; files) - enforce(!existsFile(root_path ~ fil), "The target directory already contains a '"~fil~"' file. Aborting."); + enforceDoesNotExist(fil); switch (type) { default: throw new Exception("Unknown package init type: "~type); case "minimal": initMinimalPackage(root_path); break; case "vibe.d": initVibeDPackage(root_path); break; } + writeGitignore(root_path); } void initMinimalPackage(Path root_path) @@ -107,3 +115,9 @@ if (versions.length) fil.formattedWrite(",\n\t\"versions\": [%s]", versions.map!(v => `"`~v~`"`).join(", ")); fil.write("\n}\n"); } + +void writeGitignore(Path root_path) +{ + write((root_path ~ ".gitignore").toNativeString(), + ".dub\ndocs.json\n__dummy.html\n*.o\n*.obj\n"); +} \ No newline at end of file diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index 9613553..569c07c 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -183,7 +183,7 @@ } } -private string stripUTF8Bom(string str) +string stripUTF8Bom(string str) { if( str.length >= 3 && str[0 .. 3] == [0xEF, 0xBB, 0xBF] ) return str[3 ..$]; diff --git a/source/dub/internal/vibecompat/core/file.d b/source/dub/internal/vibecompat/core/file.d index 5a806a3..e6849fe 100644 --- a/source/dub/internal/vibecompat/core/file.d +++ b/source/dub/internal/vibecompat/core/file.d @@ -134,6 +134,31 @@ } /** + Creates a symlink. +*/ +version (Windows) + alias symlinkFile = copyFile; // TODO: symlinks on Windows +else version (Posix) +{ + void symlinkFile(Path from, Path to, bool overwrite = false) + { + if (existsFile(to)) { + enforce(overwrite, "Destination file already exists."); + // remove file before copy to allow "overwriting" files that are in + // use on Linux + removeFile(to); + } + + .symlink(from.toNativeString(), to.toNativeString()); + } +} + +void symlinkFile(string from, string to) +{ + symlinkFile(Path(from), Path(to)); +} + +/** Removes a file */ void removeFile(Path path) @@ -288,4 +313,3 @@ } return ret; } - diff --git a/source/dub/package_.d b/source/dub/package_.d index b8f6f1c..d2ccc23 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -7,8 +7,13 @@ */ module dub.package_; +public import dub.recipe.packagerecipe; + import dub.compilers.compiler; import dub.dependency; +import dub.recipe.json; +import dub.recipe.sdl; + import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.core.file; @@ -22,14 +27,34 @@ import std.file; import std.range; import std.string; -import std.traits : EnumMembers; + +enum PackageFormat { json, sdl } +struct FilenameAndFormat +{ + string filename; + PackageFormat format; +} +struct PathAndFormat +{ + Path path; + PackageFormat format; + @property bool empty() { return path.empty; } + string toString() { return path.toString(); } +} + // Supported package descriptions in decreasing order of preference. -enum packageInfoFilenames = ["dub.json", /*"dub.sdl",*/ "package.json"]; -string defaultPackageFilename() { - return packageInfoFilenames[0]; -} +static immutable FilenameAndFormat[] packageInfoFiles = [ + {"dub.json", PackageFormat.json}, + /*{"dub.sdl",PackageFormat.sdl},*/ + {"package.json", PackageFormat.json} +]; + +@property string[] packageInfoFilenames() { return packageInfoFiles.map!(f => cast(string)f.filename).array; } + +@property string defaultPackageFilename() { return packageInfoFiles[0].filename; } + /** Represents a package, including its sub packages @@ -38,52 +63,86 @@ http://registry.vibed.org/package-format */ class Package { - static struct LocalPackageDef { string name; Version version_; Path path; } - private { Path m_path; - Path m_infoFile; - PackageInfo m_info; + PathAndFormat m_infoFile; + PackageRecipe m_info; Package m_parentPackage; - Package[] m_subPackages; - Path[] m_exportedPackages; } - static bool isPackageAt(Path path) + static PathAndFormat findPackageFile(Path path) { - foreach (f; packageInfoFilenames) - if (existsFile(path ~ f)) - return true; - return false; + foreach(file; packageInfoFiles) { + auto filename = path ~ file.filename; + if(existsFile(filename)) return PathAndFormat(filename, file.format); + } + return PathAndFormat(Path()); } - this(Path root, Package parent = null, string versionOverride = "") + this(Path root, PathAndFormat infoFile = PathAndFormat(), Package parent = null, string versionOverride = "") { - Json info; + RawPackage raw_package; + m_infoFile = infoFile; + try { - foreach (f; packageInfoFilenames) { - auto name = root ~ f; - if (existsFile(name)) { - m_infoFile = name; - info = jsonFromFile(m_infoFile); - break; - } + if(m_infoFile.empty) { + m_infoFile = findPackageFile(root); + if(m_infoFile.empty) throw new Exception("no package file was found, expected one of the following: "~to!string(packageInfoFiles)); } - } catch (Exception ex) throw new Exception(format("Failed to load package at %s: %s", root.toNativeString(), ex.msg)); + raw_package = rawPackageFromFile(m_infoFile); + } catch (Exception ex) throw ex;//throw new Exception(format("Failed to load package %s: %s", m_infoFile.toNativeString(), ex.msg)); - enforce(info.type != Json.Type.undefined, format("Missing package description for package at %s", root.toNativeString())); - - this(info, root, parent, versionOverride); + enforce(raw_package !is null, format("Missing package description for package at %s", root.toNativeString())); + this(raw_package, root, parent, versionOverride); } - this(Json packageInfo, Path root = Path(), Package parent = null, string versionOverride = "") + this(Json package_info, Path root = Path(), Package parent = null, string versionOverride = "") + { + this(new JsonPackage(package_info), root, parent, versionOverride); + } + + this(RawPackage raw_package, Path root = Path(), Package parent = null, string versionOverride = "") + { + PackageRecipe recipe; + + // parse the Package description + if(raw_package !is null) + { + scope(failure) logError("Failed to parse package description in %s", root.toNativeString()); + raw_package.parseInto(recipe, parent ? parent.name : null); + + if (!versionOverride.empty) + recipe.version_ = versionOverride; + + // try to run git to determine the version of the package if no explicit version was given + if (recipe.version_.length == 0 && !parent) { + try recipe.version_ = determineVersionFromSCM(root); + catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); + + if (recipe.version_.length == 0) { + logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString()); + // TODO: Assume unknown version here? + // recipe.version_ = Version.UNKNOWN.toString(); + recipe.version_ = Version.MASTER.toString(); + } else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_); + } + } + + this(recipe, root, parent); + } + + this(PackageRecipe recipe, Path root = Path(), Package parent = null) { m_parentPackage = parent; m_path = root; m_path.endsWithSlash = true; - // force the package name to be lower case - packageInfo.name = packageInfo.name.get!string.toLower(); + // use the given recipe as the basis + m_info = recipe; + + // WARNING: changed semantics here. Previously, "sourcePaths" etc. + // could overwrite what was determined here. Now the default paths + // are always added. This must be fixed somehow! // check for default string import folders foreach(defvf; ["views"]){ @@ -92,10 +151,9 @@ m_info.buildSettings.stringImportPaths[""] ~= defvf; } - string app_main_file; - auto pkg_name = packageInfo.name.get!string(); - // check for default source folders + string app_main_file; + auto pkg_name = recipe.name.length ? recipe.name : "unknown"; foreach(defsf; ["source/", "src/"]){ auto p = m_path ~ defsf; if( existsFile(p) ){ @@ -109,28 +167,6 @@ } } - // parse the JSON description - { - scope(failure) logError("Failed to parse package description in %s", root.toNativeString()); - m_info.parseJson(packageInfo); - - if (!versionOverride.empty) - m_info.version_ = versionOverride; - - // try to run git to determine the version of the package if no explicit version was given - if (m_info.version_.length == 0 && !parent) { - try m_info.version_ = determineVersionFromSCM(root); - catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); - - if (m_info.version_.length == 0) { - logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", m_info.name, this.path.toNativeString()); - // TODO: Assume unknown version here? - // m_info.version_ = Version.UNKNOWN.toString(); - m_info.version_ = Version.MASTER.toString(); - } else logDiagnostic("Determined package version using GIT: %s %s", m_info.name, m_info.version_); - } - } - // generate default configurations if none are defined if (m_info.configurations.length == 0) { if (m_info.buildSettings.targetType == TargetType.executable) { @@ -156,23 +192,6 @@ m_info.configurations ~= ConfigurationInfo("library", lib_settings); } } - - // load all sub packages defined in the package description - foreach (sub; packageInfo.subPackages.opt!(Json[])) { - enforce(!m_parentPackage, format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.", name, m_parentPackage.name)); - - if (sub.type == Json.Type.string) { - auto p = Path(sub.get!string); - p.normalize(); - enforce(!p.absolute, "Sub package paths must not be absolute: " ~ sub.get!string); - enforce(!p.startsWith(Path("..")), "Sub packages must be in a sub directory, not " ~ sub.get!string); - m_exportedPackages ~= p; - if (!path.empty) m_subPackages ~= new Package(path ~ p, this, this.vers); - } else { - m_subPackages ~= new Package(sub, root, this); - } - } - simpleLint(); } @@ -184,14 +203,13 @@ @property string vers() const { return m_parentPackage ? m_parentPackage.vers : m_info.version_; } @property Version ver() const { return Version(this.vers); } @property void ver(Version ver) { assert(m_parentPackage is null); m_info.version_ = ver.toString(); } - @property ref inout(PackageInfo) info() inout { return m_info; } + @property ref inout(PackageRecipe) info() inout { return m_info; } @property Path path() const { return m_path; } - @property Path packageInfoFile() const { return m_infoFile; } + @property Path packageInfoFilename() const { return m_infoFile.path; } @property const(Dependency[string]) dependencies() const { return m_info.dependencies; } @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } @property inout(Package) parentPackage() inout { return m_parentPackage; } - @property inout(Package)[] subPackages() inout { return m_subPackages; } - @property inout(Path[]) exportedPackages() inout { return m_exportedPackages; } + @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; } @property string[] configurations() const { @@ -224,15 +242,17 @@ auto dstFile = openFile(filename.toNativeString(), FileMode.CreateTrunc); scope(exit) dstFile.close(); dstFile.writePrettyJsonString(m_info.toJson()); - m_infoFile = filename; + m_infoFile = PathAndFormat(filename); } - inout(Package) getSubPackage(string name) inout { - foreach (p; m_subPackages) - if (p.name == this.name ~ ":" ~ name) - return p; - throw new Exception(format("Unknown sub package: %s:%s", this.name, name)); - } + /*inout(Package) getSubPackage(string name, bool silent_fail = false) + inout { + foreach (p; m_info.subPackages) + if (p.package_ !is null && p.package_.name == this.name ~ ":" ~ name) + return p.package_; + enforce(silent_fail, format("Unknown sub package: %s:%s", this.name, name)); + return null; + }*/ void warnOnSpecialCompilerFlags() { @@ -447,435 +467,62 @@ } if (name.empty()) logWarn("The package in %s has no name.", path); } -} -/// Specifying package information without any connection to a certain -/// retrived package, like Package class is doing. -struct PackageInfo { - string name; - string version_; - string description; - string homepage; - string[] authors; - string copyright; - string license; - string[] ddoxFilterArgs; - BuildSettingsTemplate buildSettings; - ConfigurationInfo[] configurations; - BuildSettingsTemplate[string] buildTypes; - Json subPackages; + private static RawPackage rawPackageFromFile(PathAndFormat file, bool silent_fail = false) { + if( silent_fail && !existsFile(file.path) ) return null; + auto f = openFile(file.path.toNativeString(), FileMode.Read); + scope(exit) f.close(); + auto text = stripUTF8Bom(cast(string)f.readAll()); - @property const(Dependency)[string] dependencies() - const { - const(Dependency)[string] ret; - foreach (n, d; this.buildSettings.dependencies) - ret[n] = d; - foreach (ref c; configurations) - foreach (n, d; c.buildSettings.dependencies) - ret[n] = d; - return ret; + final switch(file.format) { + case PackageFormat.json: + return new JsonPackage(parseJsonString(text)); + case PackageFormat.sdl: + if(silent_fail) return null; throw new Exception("SDL not implemented"); + } } - inout(ConfigurationInfo) getConfiguration(string name) - inout { - foreach (c; configurations) - if (c.name == name) - return c; - throw new Exception("Unknown configuration: "~name); - } - - void parseJson(Json json) + static abstract class RawPackage { - foreach( string field, value; json ){ - switch(field){ - default: break; - case "name": this.name = value.get!string; break; - case "version": this.version_ = value.get!string; break; - case "description": this.description = value.get!string; break; - case "homepage": this.homepage = value.get!string; break; - case "authors": this.authors = deserializeJson!(string[])(value); break; - case "copyright": this.copyright = value.get!string; break; - case "license": this.license = value.get!string; break; - case "subPackages": subPackages = value; break; - case "configurations": break; // handled below, after the global settings have been parsed - case "buildTypes": - foreach (string name, settings; value) { - BuildSettingsTemplate bs; - bs.parseJson(settings, null); - buildTypes[name] = bs; - } - break; - case "-ddoxFilterArgs": this.ddoxFilterArgs = deserializeJson!(string[])(value); break; + string package_name; // Should already be lower case + string version_; + abstract void parseInto(ref PackageRecipe package_, string parent_name); + } + private static class JsonPackage : RawPackage + { + Json json; + this(Json json) { + this.json = json; + + string nameLower; + if(json.type == Json.Type.string) { + nameLower = json.get!string.toLower(); + this.json = nameLower; + } else { + nameLower = json.name.get!string.toLower(); + this.json.name = nameLower; + this.package_name = nameLower; + + Json versionJson = json["version"]; + this.version_ = (versionJson.type == Json.Type.undefined) ? null : versionJson.get!string; } + + this.package_name = nameLower; } - - enforce(this.name.length > 0, "The package \"name\" field is missing or empty."); - - // parse build settings - this.buildSettings.parseJson(json, this.name); - - if (auto pv = "configurations" in json) { - TargetType deftargettp = TargetType.library; - if (this.buildSettings.targetType != TargetType.autodetect) - deftargettp = this.buildSettings.targetType; - - foreach (settings; *pv) { - ConfigurationInfo ci; - ci.parseJson(settings, this.name, deftargettp); - this.configurations ~= ci; - } - } - } - - Json toJson() - const { - auto ret = buildSettings.toJson(); - ret.name = this.name; - if( !this.version_.empty ) ret["version"] = this.version_; - if( !this.description.empty ) ret.description = this.description; - if( !this.homepage.empty ) ret.homepage = this.homepage; - if( !this.authors.empty ) ret.authors = serializeToJson(this.authors); - if( !this.copyright.empty ) ret.copyright = this.copyright; - if( !this.license.empty ) ret.license = this.license; - if( this.subPackages.type != Json.Type.undefined ) { - auto copy = this.subPackages.toString(); - ret.subPackages = dub.internal.vibecompat.data.json.parseJson(copy); - } - if( this.configurations ){ - Json[] configs; - foreach(config; this.configurations) - configs ~= config.toJson(); - ret.configurations = configs; - } - if( this.buildTypes.length ) { - Json[string] types; - foreach(name, settings; this.buildTypes) - types[name] = settings.toJson(); - } - if( !this.ddoxFilterArgs.empty ) ret["-ddoxFilterArgs"] = this.ddoxFilterArgs.serializeToJson(); - return ret; - } -} - -/// Bundles information about a build configuration. -struct ConfigurationInfo { - string name; - string[] platforms; - BuildSettingsTemplate buildSettings; - - this(string name, BuildSettingsTemplate build_settings) - { - enforce(!name.empty, "Configuration name is empty."); - this.name = name; - this.buildSettings = build_settings; - } - - void parseJson(Json json, string package_name, TargetType default_target_type = TargetType.library) - { - this.buildSettings.targetType = default_target_type; - - foreach(string name, value; json){ - switch(name){ - default: break; - case "name": - this.name = value.get!string(); - enforce(!this.name.empty, "Configurations must have a non-empty name."); - break; - case "platforms": this.platforms = deserializeJson!(string[])(value); break; - } - } - - enforce(!this.name.empty, "Configuration is missing a name."); - - BuildSettingsTemplate bs; - this.buildSettings.parseJson(json, package_name); - } - - Json toJson() - const { - auto ret = buildSettings.toJson(); - ret.name = name; - if( this.platforms.length ) ret.platforms = serializeToJson(platforms); - return ret; - } - - bool matchesPlatform(in BuildPlatform platform) - const { - if( platforms.empty ) return true; - foreach(p; platforms) - if( platform.matchesSpecification("-"~p) ) - return true; - return false; - } -} - -/// This keeps general information about how to build a package. -/// It contains functions to create a specific BuildSetting, targeted at -/// a certain BuildPlatform. -struct BuildSettingsTemplate { - Dependency[string] dependencies; - string systemDependencies; - TargetType targetType = TargetType.autodetect; - string targetPath; - string targetName; - string workingDirectory; - string mainSourceFile; - string[string] subConfigurations; - string[][string] dflags; - string[][string] lflags; - string[][string] libs; - string[][string] sourceFiles; - string[][string] sourcePaths; - string[][string] excludedSourceFiles; - string[][string] copyFiles; - string[][string] versions; - string[][string] debugVersions; - string[][string] importPaths; - string[][string] stringImportPaths; - string[][string] preGenerateCommands; - string[][string] postGenerateCommands; - string[][string] preBuildCommands; - string[][string] postBuildCommands; - BuildRequirements[string] buildRequirements; - BuildOptions[string] buildOptions; - - void parseJson(Json json, string package_name) - { - foreach(string name, value; json) + override void parseInto(ref PackageRecipe recipe, string parent_name) { - auto idx = std.string.indexOf(name, "-"); - string basename, suffix; - if( idx >= 0 ) basename = name[0 .. idx], suffix = name[idx .. $]; - else basename = name; - switch(basename){ - default: break; - case "dependencies": - foreach (string pkg, verspec; value) { - if (pkg.startsWith(":")) pkg = package_name ~ pkg; - enforce(pkg !in this.dependencies, "The dependency '"~pkg~"' is specified more than once." ); - this.dependencies[pkg] = deserializeJson!Dependency(verspec); - } - break; - case "systemDependencies": - this.systemDependencies = value.get!string; - break; - case "targetType": - enforce(suffix.empty, "targetType does not support platform customization."); - targetType = value.get!string().to!TargetType(); - break; - case "targetPath": - enforce(suffix.empty, "targetPath does not support platform customization."); - this.targetPath = value.get!string; - break; - case "targetName": - enforce(suffix.empty, "targetName does not support platform customization."); - this.targetName = value.get!string; - break; - case "workingDirectory": - enforce(suffix.empty, "workingDirectory does not support platform customization."); - this.workingDirectory = value.get!string; - break; - case "mainSourceFile": - enforce(suffix.empty, "mainSourceFile does not support platform customization."); - this.mainSourceFile = value.get!string; - break; - case "subConfigurations": - enforce(suffix.empty, "subConfigurations does not support platform customization."); - this.subConfigurations = deserializeJson!(string[string])(value); - break; - case "dflags": this.dflags[suffix] = deserializeJson!(string[])(value); break; - case "lflags": this.lflags[suffix] = deserializeJson!(string[])(value); break; - case "libs": this.libs[suffix] = deserializeJson!(string[])(value); break; - case "files": - case "sourceFiles": this.sourceFiles[suffix] = deserializeJson!(string[])(value); break; - case "sourcePaths": this.sourcePaths[suffix] = deserializeJson!(string[])(value); break; - case "sourcePath": this.sourcePaths[suffix] ~= [value.get!string()]; break; // deprecated - case "excludedSourceFiles": this.excludedSourceFiles[suffix] = deserializeJson!(string[])(value); break; - case "copyFiles": this.copyFiles[suffix] = deserializeJson!(string[])(value); break; - case "versions": this.versions[suffix] = deserializeJson!(string[])(value); break; - case "debugVersions": this.debugVersions[suffix] = deserializeJson!(string[])(value); break; - case "importPaths": this.importPaths[suffix] = deserializeJson!(string[])(value); break; - case "stringImportPaths": this.stringImportPaths[suffix] = deserializeJson!(string[])(value); break; - case "preGenerateCommands": this.preGenerateCommands[suffix] = deserializeJson!(string[])(value); break; - case "postGenerateCommands": this.postGenerateCommands[suffix] = deserializeJson!(string[])(value); break; - case "preBuildCommands": this.preBuildCommands[suffix] = deserializeJson!(string[])(value); break; - case "postBuildCommands": this.postBuildCommands[suffix] = deserializeJson!(string[])(value); break; - case "buildRequirements": - BuildRequirements reqs; - foreach (req; deserializeJson!(string[])(value)) - reqs |= to!BuildRequirements(req); - this.buildRequirements[suffix] = reqs; - break; - case "buildOptions": - BuildOptions options; - foreach (opt; deserializeJson!(string[])(value)) - options |= to!BuildOptions(opt); - this.buildOptions[suffix] = options; - break; - } + recipe.parseJson(json, parent_name); } } - - Json toJson() - const { - auto ret = Json.emptyObject; - if( this.dependencies !is null ){ - auto deps = Json.emptyObject; - foreach( pack, d; this.dependencies ) - deps[pack] = serializeToJson(d); - ret.dependencies = deps; - } - if (this.systemDependencies !is null) ret.systemDependencies = this.systemDependencies; - if (targetType != TargetType.autodetect) ret["targetType"] = targetType.to!string(); - if (!targetPath.empty) ret["targetPath"] = targetPath; - if (!targetName.empty) ret["targetName"] = targetName; - if (!workingDirectory.empty) ret["workingDirectory"] = workingDirectory; - if (!mainSourceFile.empty) ret["mainSourceFile"] = mainSourceFile; - foreach (suffix, arr; dflags) ret["dflags"~suffix] = serializeToJson(arr); - foreach (suffix, arr; lflags) ret["lflags"~suffix] = serializeToJson(arr); - foreach (suffix, arr; libs) ret["libs"~suffix] = serializeToJson(arr); - foreach (suffix, arr; sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr); - foreach (suffix, arr; sourcePaths) ret["sourcePaths"~suffix] = serializeToJson(arr); - foreach (suffix, arr; excludedSourceFiles) ret["excludedSourceFiles"~suffix] = serializeToJson(arr); - foreach (suffix, arr; copyFiles) ret["copyFiles"~suffix] = serializeToJson(arr); - foreach (suffix, arr; versions) ret["versions"~suffix] = serializeToJson(arr); - foreach (suffix, arr; debugVersions) ret["debugVersions"~suffix] = serializeToJson(arr); - foreach (suffix, arr; importPaths) ret["importPaths"~suffix] = serializeToJson(arr); - foreach (suffix, arr; stringImportPaths) ret["stringImportPaths"~suffix] = serializeToJson(arr); - foreach (suffix, arr; preGenerateCommands) ret["preGenerateCommands"~suffix] = serializeToJson(arr); - foreach (suffix, arr; postGenerateCommands) ret["postGenerateCommands"~suffix] = serializeToJson(arr); - foreach (suffix, arr; preBuildCommands) ret["preBuildCommands"~suffix] = serializeToJson(arr); - foreach (suffix, arr; postBuildCommands) ret["postBuildCommands"~suffix] = serializeToJson(arr); - foreach (suffix, arr; buildRequirements) { - string[] val; - foreach (i; [EnumMembers!BuildRequirements]) - if (arr & i) val ~= to!string(i); - ret["buildRequirements"~suffix] = serializeToJson(val); - } - foreach (suffix, arr; buildOptions) { - string[] val; - foreach (i; [EnumMembers!BuildOptions]) - if (arr & i) val ~= to!string(i); - ret["buildOptions"~suffix] = serializeToJson(val); - } - return ret; - } - - /// Constructs a BuildSettings object from this template. - void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path 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); - } - - void collectFiles(string method)(in string[][string] paths_map, string pattern) - { - 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); - if (!path.absolute) path = base_path ~ path; - if (!existsFile(path) || !isDir(path.toNativeString())) { - logWarn("Invalid source/import path: %s", path.toNativeString()); - continue; - } - - foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { - if (isDir(d.name)) continue; - auto src = Path(d.name).relativeTo(base_path); - __traits(getMember, dst, method)(src.toNativeString()); - } - } - } - } - - // collect files from all source/import folders - collectFiles!"addSourceFiles"(sourcePaths, "*.d"); - collectFiles!"addImportFiles"(importPaths, "*.{d,di}"); - dst.removeImportFiles(dst.sourceFiles); - collectFiles!"addStringImportFiles"(stringImportPaths, "*"); - - // ensure a deterministic order of files as passed to the compiler - dst.sourceFiles.sort(); - - getPlatformSetting!("dflags", "addDFlags")(dst, platform); - getPlatformSetting!("lflags", "addLFlags")(dst, platform); - getPlatformSetting!("libs", "addLibs")(dst, platform); - getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); - getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); - getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); - getPlatformSetting!("versions", "addVersions")(dst, platform); - getPlatformSetting!("debugVersions", "addDebugVersions")(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!("buildRequirements", "addRequirements")(dst, platform); - getPlatformSetting!("buildOptions", "addOptions")(dst, platform); - } - - void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) - const { - foreach(suffix, values; __traits(getMember, this, name)){ - if( platform.matchesSpecification(suffix) ) - __traits(getMember, dst, addname)(values); - } - } - - void warnOnSpecialCompilerFlags(string package_name, string config_name) + private static class SdlPackage : RawPackage { - auto nodef = false; - auto noprop = false; - foreach (req; this.buildRequirements) { - if (req & BuildRequirements.noDefaultFlags) nodef = true; - if (req & BuildRequirements.relaxProperties) noprop = true; - } - - if (noprop) { - logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`); - logWarn(""); - } - - if (nodef) { - logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); - logWarn(""); - } else { - string[] all_dflags; - BuildOptions all_options; - foreach (flags; this.dflags) all_dflags ~= flags; - foreach (options; this.buildOptions) all_options |= options; - .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name); + override void parseInto(ref PackageRecipe package_, string parent_name) + { + throw new Exception("SDL packages not implemented yet"); } } } -/// Returns all package names, starting with the root package in [0]. -string[] getSubPackagePath(string package_name) -{ - return package_name.split(":"); -} - -/// Returns the name of the base package in the case of some sub package or the -/// package itself, if it is already a full package. -string getBasePackageName(string package_name) -{ - return package_name.getSubPackagePath()[0]; -} - -string getSubPackageName(string package_name) -{ - return getSubPackagePath(package_name)[1 .. $].join(":"); -} private string determineVersionFromSCM(Path path) { @@ -887,7 +534,7 @@ auto git_dir_param = "--git-dir=" ~ git_dir.toNativeString(); static string exec(scope string[] params...) { - auto ret = execute(params); + auto ret = executeShell(escapeShellCommand(params)); if (ret.status == 0) return ret.output.strip; logDebug("'%s' failed with exit code %s: %s", params.join(" "), ret.status, ret.output.strip); return null; diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 365a3f8..8db4046 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -152,12 +152,13 @@ return null; } - Package getOrLoadPackage(Path path) + Package getOrLoadPackage(Path path, PathAndFormat infoFile = PathAndFormat()) { + path.endsWithSlash = true; foreach (p; getPackageIterator()) if (!p.parentPackage && p.path == path) return p; - auto pack = new Package(path); + auto pack = new Package(path, infoFile); addPackages(m_temporaryPackages, pack); return pack; } @@ -185,6 +186,15 @@ return getBestPackage(name, Dependency(version_spec)); } + Package getSubPackage(Package base_package, string sub_name, bool silent_fail) + { + foreach (p; getPackageIterator(base_package.name~":"~sub_name)) + if (p.parentPackage is base_package) + return p; + enforce(silent_fail, "Sub package "~base_package.name~":"~sub_name~" doesn't exist."); + return null; + } + /** Determines if a package is managed by DUB. @@ -205,25 +215,17 @@ { int iterator(int delegate(ref Package) del) { - int handlePackage(Package p) { - if (auto ret = del(p)) return ret; - foreach (sp; p.subPackages) - if (auto ret = del(sp)) - return ret; - return 0; - } - foreach (tp; m_temporaryPackages) - if (auto ret = handlePackage(tp)) return ret; + 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) - if (auto ret = handlePackage(p)) return ret; + if (auto ret = del(p)) return ret; // and then all packages gathered from the search path foreach( p; m_packages ) - if( auto ret = handlePackage(p) ) + if( auto ret = del(p) ) return ret; return 0; } @@ -311,8 +313,8 @@ Path zip_prefix; outer: foreach(ArchiveMember am; archive.directory) { auto path = Path(am.name); - foreach (fil; packageInfoFilenames) - if (path.length == 2 && path.head.toString == fil) { + foreach (fil; packageInfoFiles) + if (path.length == 2 && path.head.toString == fil.filename) { zip_prefix = path[0 .. $-1]; break outer; } @@ -354,12 +356,12 @@ logDiagnostic("%s file(s) copied.", to!string(countFiles)); // overwrite dub.json (this one includes a version field) - auto pack = new Package(destination, null, package_info["version"].get!string); + auto pack = new Package(destination, PathAndFormat(), null, package_info["version"].get!string); - if (pack.packageInfoFile.head != defaultPackageFilename()) { + if (pack.packageInfoFilename.head != defaultPackageFilename()) { // Storeinfo saved a default file, this could be different to the file from the zip. - removeFile(pack.packageInfoFile); - journal.remove(Journal.Entry(Journal.Type.RegularFile, Path(pack.packageInfoFile.head))); + removeFile(pack.packageInfoFilename); + journal.remove(Journal.Entry(Journal.Type.RegularFile, Path(pack.packageInfoFilename.head))); journal.add(Journal.Entry(Journal.Type.RegularFile, Path(defaultPackageFilename()))); } pack.storeInfo(); @@ -384,7 +386,7 @@ logDebug("Looking up journal"); auto journalFile = pack.path~JournalJsonFilename; if (!existsFile(journalFile)) - throw new Exception("Removal failed, no retrieval journal found for '"~pack.name~"'. Please remove the folder '%s' manually.", pack.path.toNativeString()); + throw new Exception(format("Removal failed, no retrieval journal found for '"~pack.name~"'. Please remove the folder '%s' manually.", pack.path.toNativeString())); auto packagePath = pack.path; auto journal = new Journal(journalFile); @@ -503,38 +505,6 @@ logInfo("Unregistered package: %s (version: %s)", name, ver); } - Package getTemporaryPackage(Path path, Version ver) - { - foreach (p; m_temporaryPackages) - if (p.path == path) { - enforce(p.ver == ver, format("Package in %s is refrenced with two conflicting versions: %s vs %s", path.toNativeString(), p.ver, ver)); - return p; - } - - try { - auto pack = new Package(path); - enforce(pack.name.length, "The package has no name, defined in: " ~ path.toString()); - pack.ver = ver; - addPackages(m_temporaryPackages, pack); - return pack; - } catch (Exception e) { - logDiagnostic("Error loading package at %s: %s", path.toNativeString(), e.toString().sanitize); - throw new Exception(format("Failed to add temporary package at %s: %s", path.toNativeString(), e.msg)); - } - } - - Package getTemporaryPackage(Path path) - { - foreach (p; m_temporaryPackages) - if (p.path == path) - return p; - - auto pack = new Package(path); - enforce(pack.name.length, "The package has no name, defined in: " ~ path.toString()); - addPackages(m_temporaryPackages, pack); - return pack; - } - /// For the given type add another path where packages will be looked up. void addSearchPath(Path path, LocalPackageType type) { @@ -585,7 +555,8 @@ } if (!pp) { - if (Package.isPackageAt(path)) pp = new Package(path); + auto infoFile = Package.findPackageFile(path); + if (!infoFile.empty) pp = new Package(path, infoFile); else { logWarn("Locally registered package %s %s was not found. Please run \"dub remove-local %s\".", name, ver, path.toNativeString()); @@ -624,8 +595,9 @@ try foreach( pdir; iterateDirectory(path) ){ logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name); if( !pdir.isDirectory ) continue; - auto pack_path = path ~ pdir.name; - if (!Package.isPackageAt(pack_path)) continue; + auto pack_path = path ~ (pdir.name ~ "/"); + auto packageFile = Package.findPackageFile(pack_path); + if (packageFile.empty) continue; Package p; try { if (!refresh_existing_packages) @@ -634,7 +606,7 @@ p = pp; break; } - if (!p) p = new Package(pack_path); + if (!p) p = new Package(pack_path, packageFile); addPackages(m_packages, p); } catch( Exception e ){ logError("Failed to load package in %s: %s", pack_path, e.msg); @@ -747,17 +719,27 @@ // Additionally to the internally defined subpackages, whose metadata // is loaded with the main dub.json, load all externally defined // packages after the package is available with all the data. - foreach (sub_path; pack.exportedPackages) { - auto path = pack.path ~ sub_path; - if (!existsFile(path)) { - logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString()); - continue; - } + foreach (spr; pack.subPackages) { + Package sp; + + if (spr.path.length) { + auto p = Path(spr.path); + p.normalize(); + enforce(!p.absolute, "Sub package paths must be sub paths of the parent package."); + auto path = pack.path ~ p; + if (!existsFile(path)) { + logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString()); + continue; + } + sp = new Package(path, PathAndFormat(), pack); + } else sp = new Package(spr.recipe, pack.path, pack); + // Add the subpackage. try { - dst_repos ~= new Package(path, pack); + dst_repos ~= sp; } catch (Exception e) { - logError("Package '%s': Failed to load sub-package in %s, error: %s", pack.name, path.toNativeString(), e.msg); + logError("Package '%s': Failed to load sub-package %s: %s", pack.name, + spr.path.length ? spr.path : spr.recipe.name, e.msg); logDiagnostic("Full error: %s", e.toString().sanitize()); } } diff --git a/source/dub/packagesupplier.d b/source/dub/packagesupplier.d index fe65b32..2f6ae81 100644 --- a/source/dub/packagesupplier.d +++ b/source/dub/packagesupplier.d @@ -38,6 +38,12 @@ /// returns the metadata for the package Json getPackageDescription(string packageId, Dependency dep, bool pre_release); + + /// load caches to disk + void loadCache(Path cacheDir); + + /// persist caches to disk + void storeCache(Path cacheDir); } class FileSystemPackageSupplier : PackageSupplier { @@ -79,6 +85,12 @@ return jsonFromZip(filename, "dub.json"); } + void storeCache(Path cacheDir) { + } + + void loadCache(Path cacheDir) { + } + private Path bestPackageFile(string packageId, Dependency dep, bool pre_release) { Path toPath(Version ver) { @@ -102,6 +114,7 @@ struct CacheEntry { Json data; SysTime cacheTime; } CacheEntry[string] m_metadataCache; Duration m_maxCacheTime; + bool m_metadataCacheDirty; } this(URL registry) @@ -139,12 +152,42 @@ return getBestPackage(packageId, dep, pre_release); } + void storeCache(Path cacheDir) + { + if (!m_metadataCacheDirty) return; + + auto path = cacheDir ~ cacheFileName; + if (!cacheDir.existsFile()) + mkdirRecurse(cacheDir.toNativeString()); + // TODO: method is slow due to Json escaping + writeJsonFile(path, m_metadataCache.serializeToJson()); + m_metadataCacheDirty = false; + } + + void loadCache(Path cacheDir) + { + auto path = cacheDir ~ cacheFileName; + if (!path.existsFile()) return; + + deserializeJson(m_metadataCache, jsonFromFile(path)); + m_metadataCacheDirty = false; + } + + private @property string cacheFileName() + { + import std.digest.md; + auto hash = m_registryUrl.toString.md5Of(); + return m_registryUrl.host ~ hash[0 .. $/2].toHexString().idup ~ ".json"; + } + 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); + m_metadataCacheDirty = true; } auto url = m_registryUrl ~ Path(PackagesPath ~ "/" ~ packageId ~ ".json"); @@ -154,7 +197,11 @@ auto jsonData = cast(string)download(url); Json json = parseJsonString(jsonData); + // strip readme data (to save size and time) + foreach (ref v; json["versions"]) + v.remove("readme"); m_metadataCache[packageId] = CacheEntry(json, now); + m_metadataCacheDirty = true; return json; } diff --git a/source/dub/platform.d b/source/dub/platform.d index b100755..acc9baf 100644 --- a/source/dub/platform.d +++ b/source/dub/platform.d @@ -81,7 +81,7 @@ { version(DigitalMars) return "dmd"; else version(GNU) return "gdc"; - else version(LDC) return "ldc"; + else version(LDC) return "ldc2"; else version(SDC) return "sdc"; else return null; } diff --git a/source/dub/project.d b/source/dub/project.d index c1916d9..a979e91 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -47,13 +47,12 @@ this(PackageManager package_manager, Path project_path) { Package pack; - if (!Package.isPackageAt(project_path)) { + auto packageFile = Package.findPackageFile(project_path); + if (packageFile.empty) { logWarn("There was no package description found for the application in '%s'.", project_path.toNativeString()); - auto json = Json.emptyObject; - json.name = "unknown"; - pack = new Package(json, project_path); + pack = new Package(null, project_path); } else { - pack = package_manager.getOrLoadPackage(project_path); + pack = package_manager.getOrLoadPackage(project_path, packageFile); } this(package_manager, pack); @@ -185,6 +184,27 @@ ~ "dependency to use a branch instead.", dn, SelectedVersions.defaultFile); } + + bool[string] visited; + void validateDependenciesRec(Package pack) { + foreach (name, vspec_; pack.dependencies) { + if (name in visited) continue; + visited[name] = true; + + auto basename = getBasePackageName(name); + if (m_selections.hasSelectedVersion(basename)) { + auto selver = m_selections.getSelectedVersion(basename); + if (vspec_.merge(selver) == Dependency.invalid) { + logWarn("Selected package %s %s does not match the dependency specification in package %s (%s). Need to \"dub upgrade\"?", + basename, selver, pack.name, vspec_); + } + } + + auto deppack = getDependency(name, true); + if (deppack) validateDependenciesRec(deppack); + } + } + validateDependenciesRec(m_rootPackage); } /// Rereads the applications state. @@ -203,8 +223,11 @@ Path path = vspec.path; if (!path.absolute) path = pack.path ~ path; logDiagnostic("Adding local %s", path); - p = m_packageManager.getTemporaryPackage(path); - enforce(p.name == name, format("Path based dependency %s is referenced with a wrong name: %s vs. %s", path.toNativeString(), name, p.name)); + p = m_packageManager.getOrLoadPackage(path); + if (name.canFind(':')) p = m_packageManager.getSubPackage(p, getSubPackageName(name), false); + enforce(p.name == name, + format("Path based dependency %s is referenced with a wrong name: %s vs. %s", + path.toNativeString(), name, p.name)); } if (!p) { @@ -214,7 +237,7 @@ p = m_rootPackage.basePackage; } else if (basename == m_rootPackage.basePackage.name) { vspec = Dependency(m_rootPackage.ver); - try p = m_rootPackage.getSubPackage(getSubPackageName(name)); + try p = m_packageManager.getSubPackage(m_rootPackage.basePackage, getSubPackageName(name), false); catch (Exception e) { logDiagnostic("Error getting sub package %s: %s", name, e.msg); continue; @@ -556,25 +579,46 @@ m_selections.save(path); } - private bool needsUpToDateCheck(Package pack) { - version (none) { // needs to be updated for the new package system (where no project local packages exist) - try { - auto time = m_packageSettings["dub"]["lastUpdate"].opt!(Json[string]).get(pack.name, Json("")).get!string; - if( !time.length ) return true; - return (Clock.currTime() - SysTime.fromISOExtString(time)) > dur!"days"(1); - } catch(Exception t) return true; - } else return false; + 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; + } } - private void markUpToDate(string packageId) { - logDebug("markUpToDate(%s)", packageId); + 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; + } + } + + void setUpgradeCache(Dependency[string] versions) + { + logDebug("markUpToDate"); Json create(ref Json json, string object) { if( object !in json ) json[object] = Json.emptyObject; return json[object]; } create(m_packageSettings, "dub"); - create(m_packageSettings["dub"], "lastUpdate"); - m_packageSettings["dub"]["lastUpdate"][packageId] = Json( Clock.currTime().toISOExtString() ); + m_packageSettings["dub"]["lastUpgrade"] = Clock.currTime().toISOExtString(); + + create(m_packageSettings["dub"], "cachedUpgrades"); + foreach (p, d; versions) + m_packageSettings["dub"]["cachedUpgrades"][p] = SelectedVersions.dependencyToJson(d); writeDubJson(); } @@ -877,16 +921,29 @@ m_dirty = false; } + static Json dependencyToJson(Dependency d) + { + if (d.path.empty) return Json(d.version_.toString()); + else return serializeToJson(["path": d.path.toString()]); + } + + static Dependency dependencyFromJson(Json j) + { + 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())); + else throw new Exception(format("Unexpected type for dependency: %s", j.type)); + } + Json serialize() const { Json json = serializeToJson(m_selections); Json serialized = Json.emptyObject; serialized.fileVersion = FileVersion; serialized.versions = Json.emptyObject; - foreach (p, v; m_selections) { - if (v.dep.path.empty) serialized.versions[p] = v.dep.version_.toString(); - else serialized.versions[p] = serializeToJson(["path": v.dep.path.toString()]); - } + foreach (p, v; m_selections) + serialized.versions[p] = dependencyToJson(v.dep); return serialized; } @@ -895,12 +952,7 @@ enforce(cast(int)json["fileVersion"] == FileVersion, "Mismatched dub.select.json version: " ~ to!string(cast(int)json["fileVersion"]) ~ "vs. " ~to!string(FileVersion)); clear(); scope(failure) clear(); - foreach (string p, v; json.versions) { - if (v.type == Json.Type.string) - m_selections[p] = Selected(Dependency(Version(v.get!string))); - else if (v.type == Json.Type.object) - m_selections[p] = Selected(Dependency(Path(v.path.get!string()))); - else throw new Exception("Unexpected type for dependency %s: %s", p, v.type); - } + foreach (string p, v; json.versions) + m_selections[p] = Selected(dependencyFromJson(v)); } } diff --git a/source/dub/recipe/json.d b/source/dub/recipe/json.d new file mode 100644 index 0000000..bfe010b --- /dev/null +++ b/source/dub/recipe/json.d @@ -0,0 +1,281 @@ +/** + JSON format support for PackageRecipe + + Copyright: © 2012-2014 rejectedsoftware e.K. + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Sönke Ludwig, Matthias Dondorff +*/ +module dub.recipe.json; + +import dub.compilers.compiler; +import dub.dependency; +import dub.recipe.packagerecipe; + +import dub.internal.vibecompat.data.json; + +import std.algorithm : canFind; +import std.array : startsWith; +import std.conv : to; +import std.exception : enforce; +import std.range; +import std.string : format; +import std.traits : EnumMembers; + + +void parseJson(ref PackageRecipe recipe, Json json, string parent_name) +{ + foreach (string field, value; json) { + switch (field) { + default: break; + case "name": recipe.name = value.get!string; break; + case "version": recipe.version_ = value.get!string; break; + case "description": recipe.description = value.get!string; break; + case "homepage": recipe.homepage = value.get!string; break; + case "authors": recipe.authors = deserializeJson!(string[])(value); break; + case "copyright": recipe.copyright = value.get!string; break; + case "license": recipe.license = value.get!string; break; + case "configurations": break; // handled below, after the global settings have been parsed + case "buildTypes": + foreach (string name, settings; value) { + BuildSettingsTemplate bs; + bs.parseJson(settings, null); + recipe.buildTypes[name] = bs; + } + break; + case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break; + } + } + + enforce(recipe.name.length > 0, "The package \"name\" field is missing or empty."); + + auto fullname = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name; + + // parse build settings + recipe.buildSettings.parseJson(json, fullname); + + if (auto pv = "configurations" in json) { + TargetType deftargettp = TargetType.library; + if (recipe.buildSettings.targetType != TargetType.autodetect) + deftargettp = recipe.buildSettings.targetType; + + foreach (settings; *pv) { + ConfigurationInfo ci; + ci.parseJson(settings, recipe.name, deftargettp); + recipe.configurations ~= ci; + } + } + + // parse any sub packages after the main package has been fully parsed + if (auto ps = "subPackages" in json) + recipe.parseSubPackages(fullname, ps.opt!(Json[])); +} + +Json toJson(in ref PackageRecipe recipe) +{ + auto ret = recipe.buildSettings.toJson(); + ret.name = recipe.name; + if (!recipe.version_.empty) ret["version"] = recipe.version_; + if (!recipe.description.empty) ret.description = recipe.description; + if (!recipe.homepage.empty) ret.homepage = recipe.homepage; + if (!recipe.authors.empty) ret.authors = serializeToJson(recipe.authors); + if (!recipe.copyright.empty) ret.copyright = recipe.copyright; + if (!recipe.license.empty) ret.license = recipe.license; + if (!recipe.subPackages.empty) { + Json[] jsonSubPackages = new Json[recipe.subPackages.length]; + foreach (i, subPackage; recipe.subPackages) { + if (subPackage.path !is null) { + jsonSubPackages[i] = Json(subPackage.path); + } else { + jsonSubPackages[i] = subPackage.recipe.toJson(); + } + } + ret.subPackages = jsonSubPackages; + } + if (recipe.configurations) { + Json[] configs; + foreach(config; recipe.configurations) + configs ~= config.toJson(); + ret.configurations = configs; + } + if (recipe.buildTypes.length) { + Json[string] types; + foreach (name, settings; recipe.buildTypes) + types[name] = settings.toJson(); + ret.buildTypes = types; + } + if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson(); + return ret; +} + +private void parseSubPackages(ref PackageRecipe recipe, string parent_package_name, Json[] subPackagesJson) +{ + enforce(!parent_package_name.canFind(":"), format("'subPackages' found in '%s'. This is only supported in the main package file for '%s'.", + parent_package_name, getBasePackageName(parent_package_name))); + + recipe.subPackages = new SubPackage[subPackagesJson.length]; + foreach (i, subPackageJson; subPackagesJson) { + // Handle referenced Packages + if(subPackageJson.type == Json.Type.string) { + string subpath = subPackageJson.get!string; + recipe.subPackages[i] = SubPackage(subpath, PackageRecipe.init); + } else { + PackageRecipe subinfo; + subinfo.parseJson(subPackageJson, parent_package_name); + recipe.subPackages[i] = SubPackage(null, subinfo); + } + } +} + +private void parseJson(ref ConfigurationInfo config, Json json, string package_name, TargetType default_target_type = TargetType.library) +{ + config.buildSettings.targetType = default_target_type; + + foreach (string name, value; json) { + switch (name) { + default: break; + case "name": + config.name = value.get!string(); + enforce(!config.name.empty, "Configurations must have a non-empty name."); + break; + case "platforms": config.platforms = deserializeJson!(string[])(value); break; + } + } + + enforce(!config.name.empty, "Configuration is missing a name."); + + BuildSettingsTemplate bs; + config.buildSettings.parseJson(json, package_name); +} + +private Json toJson(in ref ConfigurationInfo config) +{ + auto ret = config.buildSettings.toJson(); + ret.name = config.name; + if (config.platforms.length) ret.platforms = serializeToJson(config.platforms); + return ret; +} + +private void parseJson(ref BuildSettingsTemplate bs, Json json, string package_name) +{ + foreach(string name, value; json) + { + auto idx = std.string.indexOf(name, "-"); + string basename, suffix; + if( idx >= 0 ) basename = name[0 .. idx], suffix = name[idx .. $]; + else basename = name; + switch(basename){ + default: break; + case "dependencies": + foreach (string pkg, verspec; value) { + if (pkg.startsWith(":")) { + enforce(!package_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", package_name, pkg)); + pkg = package_name ~ pkg; + } + enforce(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once." ); + bs.dependencies[pkg] = deserializeJson!Dependency(verspec); + } + break; + case "systemDependencies": + bs.systemDependencies = value.get!string; + break; + case "targetType": + enforce(suffix.empty, "targetType does not support platform customization."); + bs.targetType = value.get!string().to!TargetType(); + break; + case "targetPath": + enforce(suffix.empty, "targetPath does not support platform customization."); + bs.targetPath = value.get!string; + break; + case "targetName": + enforce(suffix.empty, "targetName does not support platform customization."); + bs.targetName = value.get!string; + break; + case "workingDirectory": + enforce(suffix.empty, "workingDirectory does not support platform customization."); + bs.workingDirectory = value.get!string; + break; + case "mainSourceFile": + enforce(suffix.empty, "mainSourceFile does not support platform customization."); + bs.mainSourceFile = value.get!string; + break; + case "subConfigurations": + enforce(suffix.empty, "subConfigurations does not support platform customization."); + bs.subConfigurations = deserializeJson!(string[string])(value); + break; + case "dflags": bs.dflags[suffix] = deserializeJson!(string[])(value); break; + case "lflags": bs.lflags[suffix] = deserializeJson!(string[])(value); break; + case "libs": bs.libs[suffix] = deserializeJson!(string[])(value); break; + case "files": + case "sourceFiles": bs.sourceFiles[suffix] = deserializeJson!(string[])(value); break; + case "sourcePaths": bs.sourcePaths[suffix] = deserializeJson!(string[])(value); break; + 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 "versions": bs.versions[suffix] = deserializeJson!(string[])(value); break; + case "debugVersions": bs.debugVersions[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 "buildRequirements": + BuildRequirements reqs; + foreach (req; deserializeJson!(string[])(value)) + reqs |= to!BuildRequirements(req); + bs.buildRequirements[suffix] = reqs; + break; + case "buildOptions": + BuildOptions options; + foreach (opt; deserializeJson!(string[])(value)) + options |= to!BuildOptions(opt); + bs.buildOptions[suffix] = options; + break; + } + } +} + +Json toJson(in ref BuildSettingsTemplate bs) +{ + auto ret = Json.emptyObject; + if( bs.dependencies !is null ){ + auto deps = Json.emptyObject; + foreach( pack, d; bs.dependencies ) + deps[pack] = serializeToJson(d); + ret.dependencies = deps; + } + if (bs.systemDependencies !is null) ret.systemDependencies = bs.systemDependencies; + if (bs.targetType != TargetType.autodetect) ret["targetType"] = bs.targetType.to!string(); + if (!bs.targetPath.empty) ret["targetPath"] = bs.targetPath; + if (!bs.targetName.empty) ret["targetName"] = bs.targetName; + if (!bs.workingDirectory.empty) ret["workingDirectory"] = bs.workingDirectory; + if (!bs.mainSourceFile.empty) ret["mainSourceFile"] = bs.mainSourceFile; + foreach (suffix, arr; bs.dflags) ret["dflags"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.lflags) ret["lflags"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.libs) ret["libs"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.sourceFiles) ret["sourceFiles"~suffix] = serializeToJson(arr); + 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.versions) ret["versions"~suffix] = serializeToJson(arr); + foreach (suffix, arr; bs.debugVersions) ret["debugVersions"~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.buildRequirements) { + string[] val; + foreach (i; [EnumMembers!BuildRequirements]) + if (arr & i) val ~= to!string(i); + ret["buildRequirements"~suffix] = serializeToJson(val); + } + foreach (suffix, arr; bs.buildOptions) { + string[] val; + foreach (i; [EnumMembers!BuildOptions]) + if (arr & i) val ~= to!string(i); + ret["buildOptions"~suffix] = serializeToJson(val); + } + return ret; +} diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d new file mode 100644 index 0000000..4c6191c --- /dev/null +++ b/source/dub/recipe/packagerecipe.d @@ -0,0 +1,257 @@ +/** + Abstract representation of a package description file. + + Copyright: © 2012-2014 rejectedsoftware e.K. + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Sönke Ludwig, Matthias Dondorff +*/ +module dub.recipe.packagerecipe; + +import dub.compilers.compiler; +import dub.dependency; + +import dub.internal.vibecompat.core.file; +import dub.internal.vibecompat.core.log; +import dub.internal.vibecompat.inet.url; + +import std.algorithm : sort; +import std.array : join, split; +import std.exception : enforce; +import std.file; +import std.range; + + +/** + Returns the individual parts of a qualified package name. + + 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". +*/ +string[] getSubPackagePath(string package_name) +{ + return package_name.split(":"); +} + +/** + Returns the name of the top level package for a given (sub) package name. + + In case of a top level package, the qualified name is returned unmodified. +*/ +string getBasePackageName(string package_name) +{ + return package_name.getSubPackagePath()[0]; +} + +/** + Returns the qualified sub package part of the given package name. + + This is the part of the package name excluding the base package + name. See also $(D getBasePackageName). +*/ +string getSubPackageName(string package_name) +{ + return getSubPackagePath(package_name)[1 .. $].join(":"); +} + + + +/** + Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way. + + This structure is used to reason about package descriptions in isolation. + For higher level package handling, see the $(D Package) class. +*/ +struct PackageRecipe { + string name; + string version_; + string description; + string homepage; + string[] authors; + string copyright; + string license; + string[] ddoxFilterArgs; + BuildSettingsTemplate buildSettings; + ConfigurationInfo[] configurations; + BuildSettingsTemplate[string] buildTypes; + + SubPackage[] subPackages; + + @property const(Dependency)[string] dependencies() + const { + const(Dependency)[string] ret; + foreach (n, d; this.buildSettings.dependencies) + ret[n] = d; + foreach (ref c; configurations) + foreach (n, d; c.buildSettings.dependencies) + ret[n] = d; + return ret; + } + + inout(ConfigurationInfo) getConfiguration(string name) + inout { + foreach (c; configurations) + if (c.name == name) + return c; + throw new Exception("Unknown configuration: "~name); + } +} + +struct SubPackage +{ + string path; + PackageRecipe recipe; +} + + +/// Bundles information about a build configuration. +struct ConfigurationInfo { + string name; + string[] platforms; + BuildSettingsTemplate buildSettings; + + this(string name, BuildSettingsTemplate build_settings) + { + enforce(!name.empty, "Configuration name is empty."); + this.name = name; + this.buildSettings = build_settings; + } + + bool matchesPlatform(in BuildPlatform platform) + const { + if( platforms.empty ) return true; + foreach(p; platforms) + if( platform.matchesSpecification("-"~p) ) + return true; + return false; + } +} + +/// This keeps general information about how to build a package. +/// It contains functions to create a specific BuildSetting, targeted at +/// a certain BuildPlatform. +struct BuildSettingsTemplate { + Dependency[string] dependencies; + string systemDependencies; + TargetType targetType = TargetType.autodetect; + string targetPath; + string targetName; + string workingDirectory; + string mainSourceFile; + string[string] subConfigurations; + string[][string] dflags; + string[][string] lflags; + string[][string] libs; + string[][string] sourceFiles; + string[][string] sourcePaths; + string[][string] excludedSourceFiles; + string[][string] copyFiles; + string[][string] versions; + string[][string] debugVersions; + string[][string] importPaths; + string[][string] stringImportPaths; + string[][string] preGenerateCommands; + string[][string] postGenerateCommands; + string[][string] preBuildCommands; + string[][string] postBuildCommands; + BuildRequirements[string] buildRequirements; + BuildOptions[string] buildOptions; + + + /// Constructs a BuildSettings object from this template. + void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, Path 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); + } + + void collectFiles(string method)(in string[][string] paths_map, string pattern) + { + 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); + if (!path.absolute) path = base_path ~ path; + if (!existsFile(path) || !isDir(path.toNativeString())) { + logWarn("Invalid source/import path: %s", path.toNativeString()); + continue; + } + + foreach (d; dirEntries(path.toNativeString(), pattern, SpanMode.depth)) { + if (isDir(d.name)) continue; + auto src = Path(d.name).relativeTo(base_path); + __traits(getMember, dst, method)(src.toNativeString()); + } + } + } + } + + // collect files from all source/import folders + collectFiles!"addSourceFiles"(sourcePaths, "*.d"); + collectFiles!"addImportFiles"(importPaths, "*.{d,di}"); + dst.removeImportFiles(dst.sourceFiles); + collectFiles!"addStringImportFiles"(stringImportPaths, "*"); + + // ensure a deterministic order of files as passed to the compiler + dst.sourceFiles.sort(); + + getPlatformSetting!("dflags", "addDFlags")(dst, platform); + getPlatformSetting!("lflags", "addLFlags")(dst, platform); + getPlatformSetting!("libs", "addLibs")(dst, platform); + getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); + getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); + getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); + getPlatformSetting!("versions", "addVersions")(dst, platform); + getPlatformSetting!("debugVersions", "addDebugVersions")(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!("buildRequirements", "addRequirements")(dst, platform); + getPlatformSetting!("buildOptions", "addOptions")(dst, platform); + } + + void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) + const { + foreach(suffix, values; __traits(getMember, this, name)){ + if( platform.matchesSpecification(suffix) ) + __traits(getMember, dst, addname)(values); + } + } + + void warnOnSpecialCompilerFlags(string package_name, string config_name) + { + auto nodef = false; + auto noprop = false; + foreach (req; this.buildRequirements) { + if (req & BuildRequirements.noDefaultFlags) nodef = true; + if (req & BuildRequirements.relaxProperties) noprop = true; + } + + if (noprop) { + logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`); + logWarn(""); + } + + if (nodef) { + logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); + logWarn(""); + } else { + string[] all_dflags; + BuildOptions all_options; + foreach (flags; this.dflags) all_dflags ~= flags; + foreach (options; this.buildOptions) all_options |= options; + .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name); + } + } +} diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d new file mode 100644 index 0000000..3987567 --- /dev/null +++ b/source/dub/recipe/sdl.d @@ -0,0 +1,17 @@ +/** + SDL format support for PackageRecipe + + Copyright: © 2014 rejectedsoftware e.K. + License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. + Authors: Sönke Ludwig +*/ +module dub.recipe.sdl; + +import dub.recipe.packagerecipe; + +alias SDLNode = void*; // TODO + +void parseSDL(ref PackageRecipe recipe, SDLNode json, string parent_name) +{ + assert(false); +} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..adf9c15 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,13 @@ +*.a +*.lib +*.so +*.dll + +1-exec-simple/exec-simple +1-staticLib-simple/__test__library__ +2-dynLib-dep/dynlib-dep +2-sourceLib-dep/sourcelib-dep +2-staticLib-dep/staticlib-dep +custom-unittest/custom-unittest +path-subpackage-ref/test +subpackage-ref/test diff --git a/test/1-dynLib-simple/.no_build b/test/1-dynLib-simple/.no_build new file mode 100644 index 0000000..72679d2 --- /dev/null +++ b/test/1-dynLib-simple/.no_build @@ -0,0 +1 @@ +Remove me when bug with dynamic libs get fixed. diff --git a/test/1-dynLib-simple/dub.json b/test/1-dynLib-simple/dub.json new file mode 100644 index 0000000..c7747c3 --- /dev/null +++ b/test/1-dynLib-simple/dub.json @@ -0,0 +1,4 @@ +{ + "name": "dynlib-simple", + "targetType": "dynamicLibrary" +} diff --git a/test/1-dynLib-simple/source/dynlib/app.d b/test/1-dynLib-simple/source/dynlib/app.d new file mode 100644 index 0000000..78fbd42 --- /dev/null +++ b/test/1-dynLib-simple/source/dynlib/app.d @@ -0,0 +1,7 @@ +module dynlib.app; +import std.stdio; + +void entry() +{ + writeln(__FUNCTION__); +} diff --git a/test/1-exec-simple/dub.json b/test/1-exec-simple/dub.json new file mode 100644 index 0000000..016c4ea --- /dev/null +++ b/test/1-exec-simple/dub.json @@ -0,0 +1,4 @@ +{ + "name": "exec-simple", + "targetType": "executable" +} diff --git a/test/1-exec-simple/source/app.d b/test/1-exec-simple/source/app.d new file mode 100644 index 0000000..dbab869 --- /dev/null +++ b/test/1-exec-simple/source/app.d @@ -0,0 +1,6 @@ +import std.stdio; + +void main() +{ + writeln(__FUNCTION__); +} diff --git a/test/1-sourceLib-simple/.no_build b/test/1-sourceLib-simple/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/1-sourceLib-simple/.no_build diff --git a/test/1-sourceLib-simple/dub.json b/test/1-sourceLib-simple/dub.json new file mode 100644 index 0000000..0bc0c7b --- /dev/null +++ b/test/1-sourceLib-simple/dub.json @@ -0,0 +1,4 @@ +{ + "name": "sourceLib-simple", + "targetType": "sourceLibrary" +} diff --git a/test/1-sourceLib-simple/source/sourcelib/app.d b/test/1-sourceLib-simple/source/sourcelib/app.d new file mode 100644 index 0000000..87dc60e --- /dev/null +++ b/test/1-sourceLib-simple/source/sourcelib/app.d @@ -0,0 +1,7 @@ +module sourcelib.app; +import std.stdio; + +void entry() +{ + writeln(__FUNCTION__); +} diff --git a/test/1-staticLib-simple/dub.json b/test/1-staticLib-simple/dub.json new file mode 100644 index 0000000..6f01776 --- /dev/null +++ b/test/1-staticLib-simple/dub.json @@ -0,0 +1,4 @@ +{ + "name": "staticlib-simple", + "targetType": "staticLibrary" +} diff --git a/test/1-staticLib-simple/source/staticlib/app.d b/test/1-staticLib-simple/source/staticlib/app.d new file mode 100644 index 0000000..1aac61d --- /dev/null +++ b/test/1-staticLib-simple/source/staticlib/app.d @@ -0,0 +1,7 @@ +module staticlib.app; +import std.stdio; + +void entry() +{ + writeln(__FUNCTION__); +} diff --git a/test/2-dynLib-dep/dub.json b/test/2-dynLib-dep/dub.json new file mode 100644 index 0000000..393810d --- /dev/null +++ b/test/2-dynLib-dep/dub.json @@ -0,0 +1,6 @@ +{ + "name": "dynlib-dep", + "dependencies": { + "dynlib-simple": { "path": "../1-dynLib-simple/" } + } +} diff --git a/test/2-dynLib-dep/source/app.d b/test/2-dynLib-dep/source/app.d new file mode 100644 index 0000000..a316d3a --- /dev/null +++ b/test/2-dynLib-dep/source/app.d @@ -0,0 +1,7 @@ +module app; +import dynlib.app; + +void main() +{ + entry(); +} diff --git a/test/2-sourceLib-dep/dub.json b/test/2-sourceLib-dep/dub.json new file mode 100644 index 0000000..8dde9fb --- /dev/null +++ b/test/2-sourceLib-dep/dub.json @@ -0,0 +1,7 @@ +{ + "name": "sourcelib-dep", + "description": "Testing sourceLibrary dependency.", + "dependencies": { + "sourcelib-simple": { "path": "../1-sourceLib-simple/" } + } +} diff --git a/test/2-sourceLib-dep/source/app.d b/test/2-sourceLib-dep/source/app.d new file mode 100644 index 0000000..8dcae3f --- /dev/null +++ b/test/2-sourceLib-dep/source/app.d @@ -0,0 +1,7 @@ +module app; +import sourcelib.app; + +void main() +{ + entry(); +} diff --git a/test/2-staticLib-dep/dub.json b/test/2-staticLib-dep/dub.json new file mode 100644 index 0000000..754f84f --- /dev/null +++ b/test/2-staticLib-dep/dub.json @@ -0,0 +1,7 @@ +{ + "name": "staticlib-dep", + "description": "Testing staticLibrary dependency.", + "dependencies": { + "staticlib-simple": { "path": "../1-staticLib-simple/" } + } +} diff --git a/test/2-staticLib-dep/source/app.d b/test/2-staticLib-dep/source/app.d new file mode 100644 index 0000000..36aa99b --- /dev/null +++ b/test/2-staticLib-dep/source/app.d @@ -0,0 +1,7 @@ +module app; +import staticlib.app; + +void main() +{ + entry(); +} diff --git a/test/run-unittest.sh b/test/run-unittest.sh new file mode 100755 index 0000000..0581901 --- /dev/null +++ b/test/run-unittest.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +function die() { + echo -e 1>&2 "\033[0;31m"$@"\033[0m" + exit 1 +} + +function log() { + echo -e "\033[0;33m[INFO] "$@"\033[0m" +} + +if [ -z ${DUB} ]; then + die 'Error: Variable $DUB must be defined to run the tests.' +fi + +if [ -z ${COMPILER} ]; then + log '$COMPILER not defined, assuming dmd...' + COMPILER=dmd +fi + +CURR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +for pack in $(ls -d $CURR_DIR/*/); do + # First we build the packages + if [ ! -e $pack/.no_build ]; then # For sourceLibrary + if [ -e $pack/.fail_build ]; then + log "Building $pack, expected failure..." + $DUB build --force --root=$pack --compiler=$COMPILER 2>/dev/null && die "Error: Failure expected, but build passed." + else + log "Building $pack..." + $DUB build --force --root=$pack --compiler=$COMPILER || die "Build failure." + fi + fi + + # We run the ones that are supposed to be runned + if [ ! -e $pack/.no_build ] && [ ! -e $pack/.no_run ]; then + log "Running $pack..." + $DUB run --force --root=$pack --compiler=$COMPILER || die "Run failure." + fi + + # Finally, the unittest part + if [ ! -e $pack/.no_build ] && [ ! -e $pack/.no_test ]; then + log "Testing $pack..." + $DUB test --force --root=$pack --compiler=$COMPILER || die "Test failure." + fi + +done