From 5db60a2586e09a53c4834d00fb54464cc46f7f90 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Thu, 4 May 2017 11:49:30 -0700 Subject: [PATCH 1/2] build: use anaconda instead of nuitka Nuitka is not able to handle all of our dependencies correctly, so instead we're using anaconda which is essentially portable python in single directory, that we can use to simply pip intsall everything we need, pack it up and create an installer with inno setup. Currently, to build on windows(and for windows) you need to only have Inno Setup 5 installed(no need for python!) and simply run build.cmd from your prompt. It will automatically download latest miniconda and produce dvc-{version}.exe in project's root. Use dvc-{version}.exe to distribute dvc as a standalone application. It doesn't require anything from user, everything it needs is provided by our installer. --- build.cmd | 33 +++- innosetup/addSymLinkPermissions.ps1 | 62 +++++++ innosetup/modpath.iss | 219 ------------------------ innosetup/setup.iss | 253 +++++++++++++++++++++++++++- 4 files changed, 334 insertions(+), 233 deletions(-) create mode 100644 innosetup/addSymLinkPermissions.ps1 delete mode 100644 innosetup/modpath.iss diff --git a/build.cmd b/build.cmd index 34cac76120..55a0bd01bf 100644 --- a/build.cmd +++ b/build.cmd @@ -1,7 +1,30 @@ @echo OFF -rmdir /Q /S nuitka.build +set Miniconda_dir=C:\DVC +set Miniconda_bin=Miniconda3-latest-Windows-x86_64.exe +set Miniconda_url=https://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86_64.exe +set Src_dir=%cd% + +rmdir /Q /S %Miniconda_dir% +del /Q /S %Miniconda_bin% del /Q /S "dvc-*.exe" -@echo This will take a while. Go get yourself a cup of tee... -call nuitka --standalone --output-dir nuitka.build dvc.py -call "C:\Program Files (x86)\Inno Setup 5\iscc" innosetup/setup.iss -rmdir /Q /S nuitka.build + +call powershell.exe -Command (new-object System.Net.WebClient).DownloadFile('%Miniconda_url%','%Miniconda_bin%') + +REM NOTE: This is extremely important, that we use same directory that we will install dvc into. +REM The reason is that dvc.exe will have hardcoded path to python interpreter and thus we need +REM to make sure that it is the same everywhere(C:\DVC). +call .\%Miniconda_bin% /InstallationType=JustMe /RegisterPython=0 /S /D=%Miniconda_dir% + +copy innosetup\addSymLinkPermissions.ps1 %Miniconda_dir%\ + +pushd %Miniconda_dir% +call .\python -m pip install -r %Src_dir%\requirements.txt +popd + +call %Miniconda_dir%\python setup.py sdist + +pushd %Miniconda_dir% +call .\python -m pip install %Src_dir%\dist\dvc-0.8.1.tar.gz +popd + +call "C:\Program Files (x86)\Inno Setup 5\iscc" innosetup\setup.iss diff --git a/innosetup/addSymLinkPermissions.ps1 b/innosetup/addSymLinkPermissions.ps1 new file mode 100644 index 0000000000..5573c77574 --- /dev/null +++ b/innosetup/addSymLinkPermissions.ps1 @@ -0,0 +1,62 @@ +function addSymLinkPermissions($accountToAdd){ + Write-Host "Checking SymLink permissions.." + $sidstr = $null + try { + $ntprincipal = new-object System.Security.Principal.NTAccount "$accountToAdd" + $sid = $ntprincipal.Translate([System.Security.Principal.SecurityIdentifier]) + $sidstr = $sid.Value.ToString() + } catch { + $sidstr = $null + } + Write-Host "Account: $($accountToAdd)" -ForegroundColor DarkCyan + if( [string]::IsNullOrEmpty($sidstr) ) { + Write-Host "Account not found!" -ForegroundColor Red + exit -1 + } + Write-Host "Account SID: $($sidstr)" -ForegroundColor DarkCyan + $tmp = [System.IO.Path]::GetTempFileName() + Write-Host "Export current Local Security Policy" -ForegroundColor DarkCyan + secedit.exe /export /cfg "$($tmp)" + $c = Get-Content -Path $tmp + $currentSetting = "" + foreach($s in $c) { + if( $s -like "SECreateSymbolicLinkPrivilege*") { + $x = $s.split("=",[System.StringSplitOptions]::RemoveEmptyEntries) + $currentSetting = $x[1].Trim() + } + } + if( $currentSetting -notlike "*$($sidstr)*" ) { + Write-Host "Need to add permissions to SymLink" -ForegroundColor Yellow + + Write-Host "Modify Setting ""Create SymLink""" -ForegroundColor DarkCyan + + if( [string]::IsNullOrEmpty($currentSetting) ) { + $currentSetting = "*$($sidstr)" + } else { + $currentSetting = "*$($sidstr),$($currentSetting)" + } + Write-Host "$currentSetting" + $outfile = @" +[Unicode] +Unicode=yes +[Version] +signature="`$CHICAGO`$" +Revision=1 +[Privilege Rights] +SECreateSymbolicLinkPrivilege = $($currentSetting) +"@ + $tmp2 = [System.IO.Path]::GetTempFileName() + Write-Host "Import new settings to Local Security Policy" -ForegroundColor DarkCyan + $outfile | Set-Content -Path $tmp2 -Encoding Unicode -Force + Push-Location (Split-Path $tmp2) + try { + secedit.exe /configure /db "secedit.sdb" /cfg "$($tmp2)" /areas USER_RIGHTS + } finally { + Pop-Location + } + } else { + Write-Host "NO ACTIONS REQUIRED! Account already in ""Create SymLink""" -ForegroundColor DarkCyan + Write-Host "Account $accountToAdd already has permissions to SymLink" -ForegroundColor Green + return $true; + } +} diff --git a/innosetup/modpath.iss b/innosetup/modpath.iss deleted file mode 100644 index c55ec60163..0000000000 --- a/innosetup/modpath.iss +++ /dev/null @@ -1,219 +0,0 @@ -// ---------------------------------------------------------------------------- -// -// Inno Setup Ver: 5.4.2 -// Script Version: 1.4.2 -// Author: Jared Breland -// Homepage: http://www.legroom.net/software -// License: GNU Lesser General Public License (LGPL), version 3 -// http://www.gnu.org/licenses/lgpl.html -// -// Script Function: -// Allow modification of environmental path directly from Inno Setup installers -// -// Instructions: -// Copy modpath.iss to the same directory as your setup script -// -// Add this statement to your [Setup] section -// ChangesEnvironment=true -// -// Add this statement to your [Tasks] section -// You can change the Description or Flags -// You can change the Name, but it must match the ModPathName setting below -// Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked -// -// Add the following to the end of your [Code] section -// ModPathName defines the name of the task defined above -// ModPathType defines whether the 'user' or 'system' path will be modified; -// this will default to user if anything other than system is set -// setArrayLength must specify the total number of dirs to be added -// Result[0] contains first directory, Result[1] contains second, etc. -// const -// ModPathName = 'modifypath'; -// ModPathType = 'user'; -// -// function ModPathDir(): TArrayOfString; -// begin -// setArrayLength(Result, 1); -// Result[0] := ExpandConstant('{app}'); -// end; -// #include "modpath.iss" -// ---------------------------------------------------------------------------- - -procedure ModPath(); -var - oldpath: String; - newpath: String; - updatepath: Boolean; - pathArr: TArrayOfString; - aExecFile: String; - aExecArr: TArrayOfString; - i, d: Integer; - pathdir: TArrayOfString; - regroot: Integer; - regpath: String; - -begin - // Get constants from main script and adjust behavior accordingly - // ModPathType MUST be 'system' or 'user'; force 'user' if invalid - if ModPathType = 'system' then begin - regroot := HKEY_LOCAL_MACHINE; - regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; - end else begin - regroot := HKEY_CURRENT_USER; - regpath := 'Environment'; - end; - - // Get array of new directories and act on each individually - pathdir := ModPathDir(); - for d := 0 to GetArrayLength(pathdir)-1 do begin - updatepath := true; - - // Modify WinNT path - if UsingWinNT() = true then begin - - // Get current path, split into an array - RegQueryStringValue(regroot, regpath, 'Path', oldpath); - oldpath := oldpath + ';'; - i := 0; - - while (Pos(';', oldpath) > 0) do begin - SetArrayLength(pathArr, i+1); - pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1); - oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath)); - i := i + 1; - - // Check if current directory matches app dir - if pathdir[d] = pathArr[i-1] then begin - // if uninstalling, remove dir from path - if IsUninstaller() = true then begin - continue; - // if installing, flag that dir already exists in path - end else begin - updatepath := false; - end; - end; - - // Add current directory to new path - if i = 1 then begin - newpath := pathArr[i-1]; - end else begin - newpath := newpath + ';' + pathArr[i-1]; - end; - end; - - // Append app dir to path if not already included - if (IsUninstaller() = false) AND (updatepath = true) then - newpath := newpath + ';' + pathdir[d]; - - // Write new path - RegWriteStringValue(regroot, regpath, 'Path', newpath); - - // Modify Win9x path - end else begin - - // Convert to shortened dirname - pathdir[d] := GetShortName(pathdir[d]); - - // If autoexec.bat exists, check if app dir already exists in path - aExecFile := 'C:\AUTOEXEC.BAT'; - if FileExists(aExecFile) then begin - LoadStringsFromFile(aExecFile, aExecArr); - for i := 0 to GetArrayLength(aExecArr)-1 do begin - if IsUninstaller() = false then begin - // If app dir already exists while installing, skip add - if (Pos(pathdir[d], aExecArr[i]) > 0) then - updatepath := false; - break; - end else begin - // If app dir exists and = what we originally set, then delete at uninstall - if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then - aExecArr[i] := ''; - end; - end; - end; - - // If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path - if (IsUninstaller() = false) AND (updatepath = true) then begin - SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True); - - // If uninstalling, write the full autoexec out - end else begin - SaveStringsToFile(aExecFile, aExecArr, False); - end; - end; - end; -end; - -// Split a string into an array using passed delimeter -procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String); -var - i: Integer; -begin - i := 0; - repeat - SetArrayLength(Dest, i+1); - if Pos(Separator,Text) > 0 then begin - Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1); - Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text)); - i := i + 1; - end else begin - Dest[i] := Text; - Text := ''; - end; - until Length(Text)=0; -end; - - -procedure CurStepChanged(CurStep: TSetupStep); -var - taskname: String; -begin - taskname := ModPathName; - if CurStep = ssPostInstall then - if IsTaskSelected(taskname) then - ModPath(); -end; - -procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); -var - aSelectedTasks: TArrayOfString; - i: Integer; - taskname: String; - regpath: String; - regstring: String; - appid: String; -begin - // only run during actual uninstall - if CurUninstallStep = usUninstall then begin - // get list of selected tasks saved in registry at install time - appid := '{#emit SetupSetting("AppId")}'; - if appid = '' then appid := '{#emit SetupSetting("AppName")}'; - regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); - RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring); - if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); - - // check each task; if matches modpath taskname, trigger patch removal - if regstring <> '' then begin - taskname := ModPathName; - MPExplode(aSelectedTasks, regstring, ','); - if GetArrayLength(aSelectedTasks) > 0 then begin - for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin - if comparetext(aSelectedTasks[i], taskname) = 0 then - ModPath(); - end; - end; - end; - end; -end; - -function NeedRestart(): Boolean; -var - taskname: String; -begin - taskname := ModPathName; - if IsTaskSelected(taskname) and not UsingWinNT() then begin - Result := True; - end else begin - Result := False; - end; -end; diff --git a/innosetup/setup.iss b/innosetup/setup.iss index f8930cb52c..393394b01e 100644 --- a/innosetup/setup.iss +++ b/innosetup/setup.iss @@ -3,7 +3,8 @@ #define MyAppVersion "0.8.1" #define MyAppPublisher "Dmitry Petrov" #define MyAppURL "https://dataversioncontrol.com/" -#define MyAppExeName "dvc.exe" +;#define MyAppExeName "dvc.exe" +#define MyAppDir "C:\DVC" [Setup] AppId={{8258CE8A-110E-4E0D-AE60-FEE00B15F041} @@ -14,7 +15,7 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} -DefaultDirName={pf}\{#MyAppName} +;DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} AllowNoIcons=yes LicenseFile=..\LICENSE @@ -23,31 +24,265 @@ Compression=lzma SolidCompression=yes OutputDir=..\ ChangesEnvironment=yes +DefaultDirName={#MyAppDir} +DisableDirPage=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Files] -Source: "..\nuitka.build\dvc.dist\*"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#MyAppDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Tasks] Name: modifypath; Description: Adds dvc's application directory to environmental path; +Name: addsymlinkpermissions; Description: Add permission for creating symbolic links; -[Icons] -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" - -[Run] -Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent +;[Icons] +;Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" [Code] const ModPathName = 'modifypath'; ModPathType = 'user'; + SymLinkName = 'addsymlinkpermissions'; function ModPathDir(): TArrayOfString; begin setArrayLength(Result, 1) Result[0] := ExpandConstant('{app}'); end; -#include "modpath.iss" + +// ---------------------------------------------------------------------------- +// +// Inno Setup Ver: 5.4.2 +// Script Version: 1.4.2 +// Author: Jared Breland +// Homepage: http://www.legroom.net/software +// License: GNU Lesser General Public License (LGPL), version 3 +// http://www.gnu.org/licenses/lgpl.html +// +// Script Function: +// Allow modification of environmental path directly from Inno Setup installers +// +// Instructions: +// Copy modpath.iss to the same directory as your setup script +// +// Add this statement to your [Setup] section +// ChangesEnvironment=true +// +// Add this statement to your [Tasks] section +// You can change the Description or Flags +// You can change the Name, but it must match the ModPathName setting below +// Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked +// +// Add the following to the end of your [Code] section +// ModPathName defines the name of the task defined above +// ModPathType defines whether the 'user' or 'system' path will be modified; +// this will default to user if anything other than system is set +// setArrayLength must specify the total number of dirs to be added +// Result[0] contains first directory, Result[1] contains second, etc. +// const +// ModPathName = 'modifypath'; +// ModPathType = 'user'; +// +// function ModPathDir(): TArrayOfString; +// begin +// setArrayLength(Result, 1); +// Result[0] := ExpandConstant('{app}'); +// end; +// #include "modpath.iss" +// ---------------------------------------------------------------------------- + +procedure ModPath(); +var + oldpath: String; + newpath: String; + updatepath: Boolean; + pathArr: TArrayOfString; + aExecFile: String; + aExecArr: TArrayOfString; + i, d: Integer; + pathdir: TArrayOfString; + regroot: Integer; + regpath: String; + +begin + // Get constants from main script and adjust behavior accordingly + // ModPathType MUST be 'system' or 'user'; force 'user' if invalid + if ModPathType = 'system' then begin + regroot := HKEY_LOCAL_MACHINE; + regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; + end else begin + regroot := HKEY_CURRENT_USER; + regpath := 'Environment'; + end; + + // Get array of new directories and act on each individually + pathdir := ModPathDir(); + for d := 0 to GetArrayLength(pathdir)-1 do begin + updatepath := true; + + // Modify WinNT path + if UsingWinNT() = true then begin + + // Get current path, split into an array + RegQueryStringValue(regroot, regpath, 'Path', oldpath); + oldpath := oldpath + ';'; + i := 0; + + while (Pos(';', oldpath) > 0) do begin + SetArrayLength(pathArr, i+1); + pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1); + oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath)); + i := i + 1; + + // Check if current directory matches app dir + if pathdir[d] = pathArr[i-1] then begin + // if uninstalling, remove dir from path + if IsUninstaller() = true then begin + continue; + // if installing, flag that dir already exists in path + end else begin + updatepath := false; + end; + end; + + // Add current directory to new path + if i = 1 then begin + newpath := pathArr[i-1]; + end else begin + newpath := newpath + ';' + pathArr[i-1]; + end; + end; + + // Append app dir to path if not already included + if (IsUninstaller() = false) AND (updatepath = true) then + newpath := newpath + ';' + pathdir[d]; + + // Write new path + RegWriteStringValue(regroot, regpath, 'Path', newpath); + + // Modify Win9x path + end else begin + + // Convert to shortened dirname + pathdir[d] := GetShortName(pathdir[d]); + + // If autoexec.bat exists, check if app dir already exists in path + aExecFile := 'C:\AUTOEXEC.BAT'; + if FileExists(aExecFile) then begin + LoadStringsFromFile(aExecFile, aExecArr); + for i := 0 to GetArrayLength(aExecArr)-1 do begin + if IsUninstaller() = false then begin + // If app dir already exists while installing, skip add + if (Pos(pathdir[d], aExecArr[i]) > 0) then + updatepath := false; + break; + end else begin + // If app dir exists and = what we originally set, then delete at uninstall + if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then + aExecArr[i] := ''; + end; + end; + end; + + // If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path + if (IsUninstaller() = false) AND (updatepath = true) then begin + SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True); + + // If uninstalling, write the full autoexec out + end else begin + SaveStringsToFile(aExecFile, aExecArr, False); + end; + end; + end; +end; + +// Split a string into an array using passed delimeter +procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String); +var + i: Integer; +begin + i := 0; + repeat + SetArrayLength(Dest, i+1); + if Pos(Separator,Text) > 0 then begin + Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1); + Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text)); + i := i + 1; + end else begin + Dest[i] := Text; + Text := ''; + end; + until Length(Text)=0; +end; + +procedure AddSymLink(); +var + ErrorCode: Integer; + SRCdir: String; +begin + SRCdir := ExpandConstant('{app}\Scripts'); + if isUninstaller() = false then + Exec('powershell.exe', '-noexit -executionpolicy bypass -File "' + SRCdir + '\addSymLinkPermissions.ps1"', '', SW_SHOW, ewWaitUntilTerminated, ErrorCode); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + symlink_taskname: String; + modpath_taskname: String; +begin + modpath_taskname := ModPathName; + symlink_taskname := SymLinkName; + if CurStep = ssPostInstall then begin + if IsTaskSelected(modpath_taskname) then + ModPath(); + if IsTaskSelected(modpath_taskname) then + AddSymLink(); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + aSelectedTasks: TArrayOfString; + i: Integer; + taskname: String; + regpath: String; + regstring: String; + appid: String; +begin + // only run during actual uninstall + if CurUninstallStep = usUninstall then begin + // get list of selected tasks saved in registry at install time + appid := '{#emit SetupSetting("AppId")}'; + if appid = '' then appid := '{#emit SetupSetting("AppName")}'; + regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); + RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring); + if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); + + // check each task; if matches modpath taskname, trigger patch removal + if regstring <> '' then begin + taskname := ModPathName; + MPExplode(aSelectedTasks, regstring, ','); + if GetArrayLength(aSelectedTasks) > 0 then begin + for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin + if comparetext(aSelectedTasks[i], taskname) = 0 then + ModPath(); + end; + end; + end; + end; +end; + +function NeedRestart(): Boolean; +var + taskname: String; +begin + taskname := ModPathName; + if IsTaskSelected(taskname) and not UsingWinNT() then begin + Result := True; + end else begin + Result := False; + end; +end; From 9afee734f3d5caf08beaf1fb20cd0f4df88435da Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Thu, 4 May 2017 11:49:43 -0700 Subject: [PATCH 2/2] dvc: add __main__.py This allows running dvc module directly: python -m dvc --- dvc/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 dvc/__main__.py diff --git a/dvc/__main__.py b/dvc/__main__.py new file mode 100644 index 0000000000..74fb03c3da --- /dev/null +++ b/dvc/__main__.py @@ -0,0 +1,4 @@ +from dvc.main import main + +if __name__ == '__main__': + main()