├── .appveyor └── install.ps1 ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── CONVENTIONS.md ├── COPYING ├── ChangeLog ├── INSTALL.md ├── LICENSE ├── Makefile ├── NEWS.md ├── README.md ├── TODO ├── VERSION-GEN ├── appveyor.yml ├── doc ├── Makefile └── man.1 ├── setup.hint ├── src ├── Makefile ├── cygapt │ ├── __init__.py │ ├── argparser.py │ ├── copying.py │ ├── cygapt.py │ ├── exception.py │ ├── main.py │ ├── ob.py │ ├── path_mapper.py │ ├── process │ │ ├── __init__.py │ │ └── exception.py │ ├── setup.py │ ├── structure.py │ ├── test │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── case │ │ │ ├── __init__.py │ │ │ ├── exception.py │ │ │ └── py2 │ │ │ │ ├── __init__.py │ │ │ │ ├── exception.py │ │ │ │ └── minor6 │ │ │ │ ├── __init__.py │ │ │ │ └── exception.py │ │ ├── fixtures │ │ │ └── utils │ │ │ │ ├── README │ │ │ │ ├── cyglsa-bad1.dll │ │ │ │ ├── cyglsa-bad2.dll │ │ │ │ ├── cyglsa-bad3.dll │ │ │ │ ├── cyglsa-bad4.dll │ │ │ │ ├── cyglsa.dll │ │ │ │ └── cyglsa64.dll │ │ ├── test_argparser.py │ │ ├── test_cygapt.py │ │ ├── test_ob.py │ │ ├── test_path_mapper.py │ │ ├── test_process.py │ │ ├── test_setup.py │ │ ├── test_url_opener.py │ │ ├── test_utils.py │ │ └── utils.py │ ├── url_opener.py │ └── utils.py ├── cygwin.sig ├── main.py ├── postinstall-gen.sh └── setup.py ├── test └── Makefile └── tools ├── Makefile └── completion.bash /.appveyor/install.ps1: -------------------------------------------------------------------------------- 1 | # Sample script to install Python and pip under Windows 2 | # Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | $BASE_URL = "https://www.python.org/ftp/python/" 6 | 7 | 8 | function DownloadPython ($python_version, $platform_suffix) { 9 | $webclient = New-Object System.Net.WebClient 10 | $filename = "python-" + $python_version + $platform_suffix + ".msi" 11 | $url = $BASE_URL + $python_version + "/" + $filename 12 | 13 | $basedir = $pwd.Path + "\" 14 | $filepath = $basedir + $filename 15 | if (Test-Path $filename) { 16 | Write-Host "Reusing" $filepath 17 | return $filepath 18 | } 19 | 20 | # Download and retry up to 3 times in case of network transient errors. 21 | Write-Host "Downloading" $filename "from" $url 22 | $retry_attempts = 2 23 | for($i=0; $i -lt $retry_attempts; $i++){ 24 | try { 25 | $webclient.DownloadFile($url, $filepath) 26 | break 27 | } 28 | Catch [Exception]{ 29 | Start-Sleep 1 30 | } 31 | } 32 | if (Test-Path $filepath) { 33 | Write-Host "File saved at" $filepath 34 | } else { 35 | # Retry once to get the error message if any at the last try 36 | $webclient.DownloadFile($url, $filepath) 37 | } 38 | return $filepath 39 | } 40 | 41 | 42 | function InstallPython ($python_version, $architecture, $python_home) { 43 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 44 | if (Test-Path $python_home) { 45 | Write-Host $python_home "already exists, skipping." 46 | return $false 47 | } 48 | if ($architecture -eq "32") { 49 | $platform_suffix = "" 50 | } else { 51 | $platform_suffix = ".amd64" 52 | } 53 | $msipath = DownloadPython $python_version $platform_suffix 54 | Write-Host "Installing" $msipath "to" $python_home 55 | $install_log = $python_home + ".log" 56 | $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" 57 | $uninstall_args = "/qn /x $msipath" 58 | RunCommand "msiexec.exe" $install_args 59 | if (-not(Test-Path $python_home)) { 60 | Write-Host "Python seems to be installed else-where, reinstalling." 61 | RunCommand "msiexec.exe" $uninstall_args 62 | RunCommand "msiexec.exe" $install_args 63 | } 64 | if (Test-Path $python_home) { 65 | Write-Host "Python $python_version ($architecture) installation complete" 66 | } else { 67 | Write-Host "Failed to install Python in $python_home" 68 | Get-Content -Path $install_log 69 | Exit 1 70 | } 71 | } 72 | 73 | function RunCommand ($command, $command_args) { 74 | Write-Host $command $command_args 75 | Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru 76 | } 77 | 78 | 79 | function main () { 80 | InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON_DIR 81 | } 82 | 83 | main 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.pyc 4 | .* 5 | *~ 6 | /src/cygapt/version.py 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | 7 | install: 8 | - make PYTHON=python 9 | 10 | script: 11 | - make test PYTHON=python 12 | 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Authors ordered by first contribution 2 | ------------------------------------- 3 | 4 | Jan Nieuwenhuizen 5 | Christopher Cormie 6 | James Nylen 7 | Alexandre Quercia 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ------------ 3 | 4 | Cyg-apt is an open source, community-driven project. All code contributions - 5 | including those of people having commit access - must go through a pull request 6 | and be approved by a core developer before being merged. This is to ensure 7 | proper review of all the code. 8 | 9 | If you would like to help, take a look at the 10 | [list of issues](https://github.com/nylen/cyg-apt/issues). 11 | 12 | Fork the project from 13 | [github.com/nylen/cyg-apt](https://github.com/nylen/cyg-apt), create a feature 14 | branch, and send us a pull request. 15 | 16 | Please follow these guidelines for any contributions: 17 | 18 | - To ensure a consistent code base, you should make sure the code follows the 19 | [Coding Standards](CONVENTIONS.md) for the project; 20 | 21 | - Add unit tests to prove that the bug is fixed or that the new feature 22 | actually works; 23 | 24 | - Try hard not to break backward compatibility (if you must do so, try to 25 | provide a compatibility layer to support the old way) -- patches that break 26 | backward compatibility have less chance to be merged; 27 | 28 | - Patches to fix coding standards in existing code are welcome, but don't fix 29 | coding standards along with other changes because it makes the code review 30 | more difficult; 31 | 32 | - The pull request description must include the following checklist at the 33 | top to ensure that contributions may be reviewed without needless feedback 34 | loops and that your contributions can be included into cyg-apt as quickly 35 | as possible: 36 | 37 | ``` 38 | | Q | A 39 | | --------------- | --- 40 | | Bug fix? | [yes|no] 41 | | New feature? | [yes|no] 42 | | BC breaks? | [yes|no] 43 | | Deprecations? | [yes|no] 44 | | Tests pass? | [yes|no] 45 | | Related tickets | [comma separated list of tickets related by the PR] 46 | | License | GNU GPLv3 47 | 48 | ``` 49 | 50 | If the code is not finished yet because you don't have time to finish it or 51 | because you want early feedback on your work, add a 52 | [task list](https://help.github.com/articles/writing-on-github#task-lists): 53 | 54 | ``` 55 | - [ ] finish the code 56 | - [ ] gather feedback for my changes 57 | ``` 58 | 59 | As long as you have unfinished items in the task list, please prefix the 60 | pull request title with `[WIP]`. 61 | 62 | 63 | Thank you for contributing. 64 | -------------------------------------------------------------------------------- /CONVENTIONS.md: -------------------------------------------------------------------------------- 1 | Coding Conventions for cyg-apt 2 | ============================== 3 | 4 | Extends and overwrite [PEP 8][] and [PEP 257][] 5 | 6 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", 7 | "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be 8 | interpreted as described in [RFC 2119][]. 9 | 10 | [RFC 2119]: http://www.ietf.org/rfc/rfc2119.txt 11 | [PEP 8]: http://www.python.org/dev/peps/pep-0008/ 12 | [PEP 257]: http://www.python.org/dev/peps/pep-0257/ 13 | 14 | 15 | General 16 | ------- 17 | 18 | * Use classes as much as possible. 19 | * A file SHOULD contain only one class (module == class). 20 | * Each statement MUST end with a semicolon `;`. 21 | 22 | 23 | Idioms 24 | ------ 25 | 26 | * Use only format() method to formatting strings. 27 | * `print` is a function. 28 | 29 | 30 | Files 31 | ----- 32 | 33 | * Code MUST use 4 spaces for indenting, not tabs. 34 | * All Python files MUST use the Unix LF (line feed) line ending. 35 | * All Python files MUST end with a single blank line. 36 | 37 | 38 | Classes 39 | ------- 40 | 41 | * Class names MUST be declared in `StudlyCaps`. 42 | * Method names MUST be declared in `camelCase`. 43 | * Property/Method names MUST start but not ending with 44 | TWO underscores `__` to indicate private visibility. 45 | * Property/Method names MUST start but not ending with 46 | ONE underscores `_` to indicate protected visibility. 47 | * `exception` module SHOULD contain all `Exception` class. 48 | * You can put an `Exception` definition at the end of a file 49 | if the file is the only one that uses that exception. 50 | * Every `Exception` class MUST end with `Exception`. 51 | 52 | 53 | Example 54 | ------- 55 | ```Python 56 | import sys; 57 | 58 | from package.class_name import ClassName; 59 | 60 | class ClassName(): 61 | def __init__(self, arg1, arg2): 62 | """Python magic method""" 63 | self.propertyName = "{1}".format("Public property"); 64 | self._propertyName = arg1; # Protected property 65 | self.__propertyName = arg2; # Private property 66 | 67 | print(self.propertyName, end="", file=sys.stderr); 68 | 69 | def methodName(self): 70 | """Public method""" 71 | pass; 72 | 73 | def _methodName(self): 74 | """protected method""" 75 | pass; 76 | 77 | def __methodName(self): 78 | """private method""" 79 | pass; 80 | 81 | class ClassNameException(Exception): 82 | """A ClassName Exception""" 83 | pass; 84 | 85 | ``` 86 | 87 | 88 | String 89 | ------ 90 | 91 | * Double quotes for text 92 | * Single quotes for anything that behaves like an identifier 93 | * Double quoted raw string literals for regexps 94 | * Tripled double quotes for docstrings 95 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ========================= 3 | 4 | Copyright (C) 1994-1996, 1999-2002, 2004-2012 Free Software Foundation, 5 | Inc. 6 | 7 | Copying and distribution of this file, with or without modification, 8 | are permitted in any medium without royalty provided the copyright 9 | notice and this notice are preserved. This file is offered as-is, 10 | without warranty of any kind. 11 | 12 | Basic Installation 13 | ------------------ 14 | 15 | Briefly, the shell commands `make; make install` should 16 | build, and install this package. 17 | 18 | The simplest way to compile this package is: 19 | 20 | 1. `cd` to the directory containing the package's source code and type 21 | `make package` to compile the package. 22 | 23 | 2. Optionally, type `make test` to run any self-tests that come with 24 | the package, generally using the just-built uninstalled binaries. 25 | 26 | 3. Type `make install` to install the programs and any data files and 27 | documentation. When installing into a prefix owned by root, it is 28 | recommended that the package be configured and built as a regular 29 | user, and only the `make install` phase executed with root 30 | privileges. 31 | 32 | 4. Optionally, type `make installtest` to repeat any self-tests, but 33 | this time using the binaries in their final installed location. 34 | This target does not install anything. Running this target as a 35 | regular user, particularly if the prior `make install` required 36 | root privileges, verifies that the installation completed 37 | correctly. 38 | 39 | 5. You can remove the program binaries and object files from the 40 | source code directory by typing `make clean`. To also remove all 41 | genereted fils type `make mrproper`. 42 | 43 | Installation Names 44 | ------------------ 45 | 46 | By default, `make install` installs the package's commands under 47 | `/usr/bin`, share files under `/usr/share`, etc. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | cyg-apt - a Cygwin package manager 2 | Copyright (C) 2002-2009 Chris Cormie 3 | 2002-2009 Jan Nieuwenhuizen 4 | 2012 James Nylen 5 | 2012-2014 Alexandre Quercia 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Default target 2 | all:: build tools doc 3 | 4 | # vars utils 5 | UTIL_SPACE := $() # 6 | 7 | # Programs 8 | SHELL_PATH = /bin/bash 9 | CP = /bin/cp -f 10 | RM = /bin/rm -f --preserve-root 11 | MV = /bin/mv 12 | MKDIR = /bin/mkdir -p 13 | INSTALL = /bin/install 14 | GZ = /bin/gzip --best 15 | TAR = /bin/tar 16 | PYTHON = /usr/bin/python 17 | MAKE ?= /usr/bin/make 18 | 19 | # Source directories 20 | SD_ROOT = $(subst $(UTIL_SPACE),\$(UTIL_SPACE),$(shell pwd)) 21 | SD_BUILD = $(SD_ROOT)/build 22 | SD_SRC = $(SD_ROOT)/src 23 | SD_TEST = $(SD_ROOT)/test 24 | SD_DIST = $(SD_ROOT)/dist 25 | SD_DOC = $(SD_ROOT)/doc 26 | SD_TOOLS = $(SD_ROOT)/tools 27 | 28 | # source environement 29 | VERSION = 1.1.0rc1 30 | VERSION_FILE = VERSION-FILE~ 31 | $(VERSION_FILE): FORCE 32 | @$(SHELL_PATH) ./VERSION-GEN 33 | -include $(VERSION_FILE) 34 | 35 | # install environement 36 | EXENAME = cyg-apt 37 | 38 | # Install directories 39 | ID_ROOT = 40 | ID_PREFIX = usr 41 | ID_LOCALSTATE = var 42 | ID_SYSCONF = etc 43 | ID_LIBEXEC = $(ID_PREFIX)/lib 44 | ID_EXEC = $(ID_PREFIX)/bin 45 | ID_DATA = $(ID_PREFIX)/share 46 | ID_MAN = $(ID_DATA)/man 47 | ID_INFO = $(ID_DATA)/info 48 | 49 | build: FORCE 50 | @cd $(SD_SRC); $(MAKE) 51 | 52 | doc: FORCE 53 | @cd $(SD_DOC); $(MAKE) 54 | 55 | tools: FORCE 56 | @cd $(SD_TOOLS); $(MAKE) 57 | 58 | test: build FORCE 59 | @cd $(SD_TEST); $(MAKE) 60 | 61 | installtest: install FORCE 62 | @cd $(SD_TEST); $(MAKE) $@ 63 | 64 | install: FORCE 65 | @cd $(SD_SRC); $(MAKE) $@ 66 | @cd $(SD_DOC); $(MAKE) $@ 67 | @cd $(SD_TOOLS); $(MAKE) $@ 68 | 69 | $(SD_DIST)/$(EXENAME)-$(VERSION): build doc tools 70 | $(MKDIR) $(SD_DIST)/$(EXENAME)-$(VERSION) 71 | cd $(SD_BUILD); pwd; $(TAR) -jcf $(SD_DIST)/$(EXENAME)-$(VERSION)/$(EXENAME)-$(VERSION).tar.bz2 * 72 | git archive --prefix="$(EXENAME)-$(VERSION)/" --format=tar HEAD | bzip2 -c > $(SD_DIST)/$(EXENAME)-$(VERSION)/$(EXENAME)-$(VERSION)-src.tar.bz2 73 | $(CP) setup.hint $(SD_DIST)/$(EXENAME)-$(VERSION) 74 | 75 | package: $(SD_DIST)/$(EXENAME)-$(VERSION) 76 | 77 | packageclean: 78 | $(RM) -r $(SD_DIST) 79 | 80 | clean: FORCE 81 | @cd $(SD_TEST); $(MAKE) $@ 82 | @cd $(SD_SRC); $(MAKE) $@ 83 | @cd $(SD_DOC); $(MAKE) $@ 84 | @cd $(SD_TOOLS); $(MAKE) $@ 85 | $(RM) $(VERSION_FILE) 86 | 87 | mrproper: FORCE clean packageclean 88 | $(RM) -r $(SD_BUILD) 89 | 90 | .PHONY: FORCE 91 | .EXPORT_ALL_VARIABLES: 92 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | News 2 | ==== 3 | 4 | * 2.0.0 () 5 | 6 | * Removed the `setup_ini` configuration field 7 | 8 | Before: 9 | 10 | The `setup.ini` database is located at the following paths: 11 | 12 | - `///setup.ini` 13 | 14 | - according to the value of the `setup_ini` configuration field, 15 | with the default one `/etc/setup/setup.ini` 16 | 17 | After: 18 | 19 | The `setup.ini` database is only located at the following path: 20 | 21 | - `///setup.ini` 22 | 23 | * Removed the `md5` command, use `checksum` instead. 24 | 25 | * 1.2.0 () 26 | 27 | * Added `checksum` command. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cyg-apt 2 | ======= 3 | 4 | A Cygwin command line package manager. 5 | 6 | Like `apt-get`, `cyg-apt` allows you to install and remove packages on the Cygwin command line, and provides other package management functions. 7 | 8 | This project is a fork of http://code.google.com/p/cyg-apt/ with extensive improvements and lots of bugfixes by [@alquerci](https://github.com/alquerci), [@nylen](https://github.com/nylen), and others. 9 | 10 | Requirements 11 | ------------ 12 | 13 | * `cygwin` 1.7+ 14 | * `gnupg` 1.4+ 15 | * `python` 2.6+, <3.0 16 | * `python-argparse` 1.2+ 17 | * `xz` (should be installed with Cygwin by default) 18 | 19 | 20 | Build requirements 21 | ------------------ 22 | 23 | * `make` 3.80+ 24 | * `git` 1.7+ 25 | 26 | 27 | Install instructions 28 | -------------------- 29 | 30 | Briefly the following commands should build, test and install this package. 31 | 32 | $ make 33 | $ make test 34 | $ make install 35 | 36 | See the [`INSTALL.md`](INSTALL.md) file for more detailed instructions. 37 | 38 | 39 | Usage 40 | ----- 41 | 42 | `cyg-apt` is similar to `apt-get`. You can use it to install and remove packages (and more) from the Cygwin command prompt: 43 | 44 | $ cyg-apt install gdb 45 | ... 46 | $ gdb 47 | (gdb) 48 | $ cyg-apt remove gdb 49 | 50 | Type `cyg-apt --help` or `man cyg-apt` to see all commands and options: 51 | 52 | ``` 53 | Usage: cyg-apt [OPTION]... COMMAND [PACKAGE]... 54 | 55 | Commands: 56 | setup : create cyg-apt configuration file, it overwrite with -f option 57 | update : fetch current package database from mirror 58 | ball : print tarball name 59 | download : download package (only, do not install) 60 | filelist : list files installed by given packages 61 | find : find package containing file 62 | help : this help message 63 | install : download and install packages with dependencies 64 | list : list installed packages 65 | checksum : check digest of cached package against database 66 | missing : print missing dependencies for package 67 | new : list new (upgradable) packages in distribution 68 | purge : uninstall packages and delete from cache 69 | remove : uninstall packages 70 | requires : print requires: for package 71 | search : search all package descriptions for string 72 | show : print package description 73 | source : download source package 74 | upgrade : all installed packages 75 | url : print tarball url 76 | version : print installed version 77 | 78 | Options: 79 | -d, --download download only 80 | -h, --help show brief usage 81 | -m, --mirror=URL use mirror 82 | -t, --dist=NAME set dist name (curr, test, prev) 83 | -x, --no-deps ignore dependencies 84 | -s, --regexp search as regex pattern 85 | -f, --nobarred add/remove packages cyg-apt depends on 86 | -X, --no-verify do not verify setup.ini signatures 87 | -y, --nopostinstall do not run postinstall scripts 88 | -z, --nopostremove do not run preremove/postremove scripts 89 | -q, --quiet loggable output - no progress indicator 90 | ``` 91 | 92 | 93 | Contributing 94 | ------------ 95 | 96 | Cyg-apt is an open source, community-driven project. All code contributions - 97 | including those of people having commit access - must go through a pull request 98 | and be approved by a core developer before being merged. This is to ensure 99 | proper review of all the code. 100 | 101 | If you would like to help, take a look at the 102 | [list of issues](https://github.com/nylen/cyg-apt/issues). 103 | 104 | See the [`CONTRIBUTING.md`](CONTRIBUTING.md) file for more detailed instructions. 105 | 106 | 107 | Acknowledgments 108 | --------------- 109 | 110 | The original cyg-apt was written by Jan Nieuwenhuizen . 111 | 112 | For a list of authors, please see the `AUTHORS` files. 113 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Add cygport support 2 | Add travis test 3 | Add missing tests 4 | [CygApt] Remove cygcheck dependency to control package integrity (_integrity_control) 5 | For it help you to the source code of cygcheck under the cygwin package 6 | [ArgParser] Update usage with pretty argparse format ( subparser ? ) 7 | Add language translation 8 | [Makefile] Add `make uninstall` 9 | [Makefile] Add configure script for check dependency it must be set macro values 10 | -------------------------------------------------------------------------------- /VERSION-GEN: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # (c) GIT-VERSION-GEN - github.com/git/git 3 | 4 | if [ -z "$VERSION_FILE" ]; 5 | then 6 | VF="VERSION-FILE"; 7 | else 8 | VF="$VERSION_FILE"; 9 | fi 10 | 11 | LF=' 12 | ' 13 | 14 | # First see if there is a version file (included in release tarballs), 15 | # then try git-describe, then default. 16 | if test -f version 17 | then 18 | VN=$(cat version) || VN="$VERSION" 19 | elif test -d .git -o -f .git && 20 | VN=$(git describe --match "v[0-9]*" --tags --abbrev=7 HEAD 2>/dev/null) && 21 | case "$VN" in 22 | *$LF*) (exit 1) ;; 23 | v[0-9]*) 24 | git update-index -q --refresh 25 | test -z "$(git diff-index --name-only HEAD --)" || 26 | VN="$VN-dirty" ;; 27 | esac 28 | then 29 | # Format into cygwin version major.minor.micro-release[.] 30 | oldIFS="$IFS"; 31 | IFS="-"; 32 | i=0; 33 | VT=""; 34 | for part in $VN; 35 | do 36 | case "$i" 37 | in 38 | '0') 39 | VT+="$part"; 40 | ;; 41 | '1') 42 | VT+="-$part"; 43 | ;; 44 | *) 45 | VT+=".$part"; 46 | ;; 47 | esac; 48 | (( i++ )); 49 | done; 50 | unset i; 51 | IFS="$oldIFS" 52 | VN="$VT"; 53 | else 54 | VN="$VERSION" 55 | fi 56 | 57 | VN=$(expr "$VN" : v*'\(.*\)') 58 | 59 | if test -r $VF 60 | then 61 | VC=$(sed -e 's/^VERSION = //' <$VF) 62 | else 63 | VC=unset 64 | fi 65 | 66 | test "$VN" = "$VC" || { 67 | echo >&2 "VERSION = $VN" 68 | echo "VERSION = $VN" >$VF 69 | } 70 | 71 | exit 0; 72 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | global: 3 | CYG_ROOT: C:/cygwin 4 | CYG_MIRROR: http://cygwin.uib.no 5 | CYG_CACHE: C:/cygwin/var/cache/setup 6 | 7 | matrix: 8 | - 9 | CYG_ARCH: x86_64 10 | PYTHON: C:/cygwin/bin/python2.7.exe 11 | PYTHON_PLATFORM: cygwin 12 | 13 | - 14 | CYG_ARCH: x86 15 | PYTHON: C:/python27/python.exe 16 | PYTHON_DIR: 'C:\\python27' 17 | PYTHON_VERSION: '2.7' 18 | 19 | - 20 | CYG_ARCH: x86 21 | PYTHON: C:/python27/python.exe 22 | PYTHON_DIR: 'C:\\python27' 23 | PYTHON_VERSION: '2.7' 24 | PYTHON_ARCH: '32' 25 | 26 | - 27 | CYG_ARCH: x86_64 28 | PYTHON: C:/python27/python.exe 29 | PYTHON_DIR: 'C:\\python27' 30 | PYTHON_VERSION: '2.7' 31 | 32 | - 33 | CYG_ARCH: x86_64 34 | PYTHON: C:/python27/python.exe 35 | PYTHON_DIR: 'C:\\python27' 36 | PYTHON_VERSION: '2.7' 37 | PYTHON_ARCH: '32' 38 | 39 | - 40 | CYG_ARCH: x86 41 | PYTHON: C:/python26/python.exe 42 | PYTHON_DIR: 'C:\\python26' 43 | PYTHON_VERSION: '2.6' 44 | 45 | - 46 | CYG_ARCH: x86 47 | PYTHON: C:/python26/python.exe 48 | PYTHON_DIR: 'C:\\python26' 49 | PYTHON_VERSION: '2.6' 50 | PYTHON_ARCH: '32' 51 | 52 | - 53 | CYG_ARCH: x86_64 54 | PYTHON: C:/python26/python.exe 55 | PYTHON_DIR: 'C:\\python26' 56 | PYTHON_VERSION: '2.6' 57 | 58 | - 59 | CYG_ARCH: x86_64 60 | PYTHON: C:/python26/python.exe 61 | PYTHON_DIR: 'C:\\python26' 62 | PYTHON_VERSION: '2.6' 63 | PYTHON_ARCH: '32' 64 | 65 | init: 66 | - 'echo OS architecture: %PROCESSOR_ARCHITECTURE%' 67 | 68 | install: 69 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" dir "C:/python*"' 70 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" if exist "%PYTHON_DIR%" rmdir /s /q "%PYTHON_DIR%"' 71 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" dir "C:/python*"' 72 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" if "%PYTHON_ARCH%" == "" set PYTHON_ARCH=64' 73 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" powershell ./.appveyor/install.ps1' 74 | - 'if not "%PYTHON_PLATFORM%" == "cygwin" dir "C:/python*"' 75 | - 'appveyor DownloadFile http://cygwin.com/setup-%CYG_ARCH%.exe -FileName setup.exe' 76 | - 'setup.exe -qnNdO -R "%CYG_ROOT%" -s "%CYG_MIRROR%" -l "%CYG_CACHE%" -P make -P python -P gnupg > NUL' 77 | - '%CYG_ROOT%/bin/bash -lc "cygcheck -dc cygwin"' 78 | - '%PYTHON% --version' 79 | - '%PYTHON% -c "import sys; print(''Python architecture: {0}''.format(''x64'' if sys.maxsize > 2**32 else ''x86''))"' 80 | - '%PYTHON% -c "import sys; print(''Python platform: {0}''.format(sys.platform));"' 81 | - 'if "2.6" == "%PYTHON_VERSION%" appveyor DownloadFile https://raw.githubusercontent.com/bewest/argparse/master/argparse.py -FileName %PYTHON_DIR%/Lib/site-packages/argparse.py' 82 | 83 | build_script: 84 | - '%CYG_ROOT%/bin/bash -lc "cd \"$OLDPWD\"; make PYTHON=\"$PYTHON\""' 85 | 86 | test_script: 87 | - '%CYG_ROOT%/bin/bash -lc "cd \"$OLDPWD\"; make test PYTHON=\"$PYTHON\""' 88 | - '%CYG_ROOT%/bin/bash -lc "cd \"$OLDPWD\"; make install"' 89 | - '%CYG_ROOT%/bin/bash -lc "cd \"$OLDPWD\"; make installtest PYTHON=\"$PYTHON\""' 90 | - ps: 'cp $env:CYG_ROOT/home/$env:USERNAME/.cyg-apt -destination $env:HOMEPATH' 91 | - '%PYTHON% %CYG_ROOT%/bin/cyg-apt update -qX' 92 | - '%PYTHON% %CYG_ROOT%/bin/cyg-apt ball cygwin> ball~' 93 | - ps: 'Get-Content ball~ | set path; cmd /c $env:CYG_ROOT/bin/cygpath -w $path> ball~' 94 | - ps: 'Get-Content ball~' 95 | - ps: 'if (-not(Get-Content ball~ | Test-Path)) { Exit 1 }' 96 | - 'del ball~' 97 | # test install on long cwd 98 | - 'for /L %%X in (0, 1, 45) do (mkdir %%X_d) & (cd %%X_d)' 99 | - 'cd' 100 | - '%PYTHON% %CYG_ROOT%/bin/cyg-apt -q install ping' 101 | - 'cd "%APPVEYOR_BUILD_FOLDER%"' 102 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | SRC = $(wildcard *.1) 2 | D_BUILD = $(SD_BUILD)/$(ID_MAN) 3 | D_MAN1 = man1 4 | MAN1 = $(D_MAN1)/$(EXENAME).1.gz 5 | D_DOC = $(SD_BUILD)/$(ID_DATA)/doc/$(EXENAME) 6 | ID_DOC = $(ID_ROOT)/$(ID_DATA)/doc 7 | 8 | INSTALL_DOCS = 9 | INSTALL_DOCS += ../COPYING 10 | INSTALL_DOCS += ../LICENSE 11 | INSTALL_DOCS += ../README.md 12 | INSTALL_DOCS += ../ChangeLog 13 | INSTALL_DOCS += ../INSTALL.md 14 | INSTALL_DOCS += ../TODO 15 | INSTALL_DOCS += ../AUTHORS 16 | 17 | all:: $(D_BUILD)/$(MAN1) doc 18 | 19 | $(D_BUILD): 20 | $(MKDIR) $(D_BUILD)/$(D_MAN1) 21 | 22 | $(D_BUILD)/$(MAN1): $(SRC) $(D_BUILD) 23 | $(GZ) -c "$<" > "$@" 24 | $(GZ) -t "$@" 25 | 26 | doc: $(INSTALL_DOCS) 27 | @$(MKDIR) $(D_DOC) 28 | $(CP) $^ $(D_DOC) 29 | 30 | doc-install: doc 31 | $(INSTALL) -d -m 755 $(ID_DOC) 32 | $(CP) -r $(D_DOC) $(ID_DOC) 33 | 34 | install: $(D_BUILD)/$(MAN1) doc-install 35 | $(INSTALL) -d -m 755 $(ID_ROOT)/$(ID_MAN)/$(D_MAN1) 36 | $(INSTALL) "$<" $(ID_ROOT)/$(ID_MAN)/$(D_MAN1) 37 | 38 | clean: FORCE 39 | 40 | 41 | .PHONY: FORCE 42 | .EXPORT_ALL_VARIABLES: 43 | -------------------------------------------------------------------------------- /doc/man.1: -------------------------------------------------------------------------------- 1 | .\" Process this file with 2 | .\" groff -man -Tascii man.1 3 | .\" 4 | .TH CYG\-APT 1 "2014-08-31" 5 | .SH NAME 6 | cyg\-apt \- a Cygwin package manager utility \-\- command-line interface 7 | .SH SYNOPSIS 8 | .nf 9 | .PP 10 | .BR "cyg\-apt setup " [ \-fXq ] " " [ \-m " " \fIMIRROR_URL ] 11 | .PP 12 | .BR "cyg\-apt update " [ \-Xq ] " " [ \-m " " \fIMIRROR_URL ] 13 | .PP 14 | .BR "cyg\-apt upgrade " [ \-dxfyzq ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] 15 | .PP 16 | .BR "cyg\-apt install " [ \-dxfyq ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE... 17 | .PP 18 | .BR "cyg\-apt remove " [ \-fz ] " " \fIPACKAGE... 19 | .PP 20 | .BR "cyg\-apt purge " [ \-fz ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE... 21 | .PP 22 | .BR "cyg\-apt source " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 23 | .PP 24 | .BR "cyg\-apt show " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " \fIPACKAGE 25 | .PP 26 | .BR "cyg\-apt search " [ \-sq ] " " [ \-m " " \fIMIRROR_URL ] " " \fISTRING 27 | .PP 28 | .BR "cyg\-apt requires " [ \-xq ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 29 | .PP 30 | .BR "cyg\-apt list " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] 31 | .PP 32 | .BR "cyg\-apt version " [ \fIPACKAGE ] 33 | .PP 34 | .BR "cyg\-apt find " \fIFILE 35 | .PP 36 | .BR "cyg\-apt ball " [ \-mq " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 37 | .PP 38 | .BR "cyg\-apt download " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 39 | .PP 40 | .BR "cyg\-apt filelist " \fIPACKAGE 41 | .PP 42 | .BR "cyg\-apt md5 " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 43 | .PP 44 | .BR "cyg\-apt checksum " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 45 | .PP 46 | .BR "cyg\-apt missing " [ \-xq ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 47 | .PP 48 | .BR "cyg\-apt new " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] 49 | .PP 50 | .BR "cyg\-apt url " [ \-q ] " " [ \-m " " \fIMIRROR_URL ] " " [ \-t " (" prev | curr | test )] " " \fIPACKAGE 51 | .PP 52 | .BR "cyg\-apt " [ help ] " " [ \-h ] 53 | .fi 54 | .SH DESCRIPTION 55 | .PP 56 | \fBcyg\-apt\fR is a command\-line tool for handling packages. 57 | Like \fBapt\-get\fR, \fBcyg\-apt\fR allows you to install and remove packages 58 | on the Cygwin command line, and provides other package management operations. 59 | .PP 60 | Unless the \fB\-h\fR, or \fB\-\-help\fR option is given, one of the commands 61 | below must be present. 62 | .SS Commands 63 | .TP 64 | .B setup 65 | setup is run automatically when \fBcyg\-apt\fR is installed. 66 | If for any reason you wish to reset your configuration file \fI~/.cyg\-apt\fR, 67 | you can add the \fB\-f\fR option. 68 | The configuration information therein is taken from your last Cygwin setup.exe run. 69 | .TP 70 | .B update 71 | update is used to resynchronize the package index files from their sources. 72 | The indexes of available packages are fetched from the location specified by 73 | the \fImirror\fR configuration. 74 | For example, this command retrieves setup.ini file, so that information about 75 | new and updated packages is available. 76 | An update should always be performed before an upgrade. 77 | .TP 78 | .B upgrade 79 | upgrade is used to install the newest versions of all packages currently 80 | installed on the system from the mirror specified by the configuration. 81 | Packages currently installed with new versions available are retrieved and 82 | upgraded. 83 | An update must be performed first so that \fBcyg\-apt\fR knows that new versions 84 | of packages are available. 85 | Use with caution, there are small number of packages \fBcyg\-apt\fR will not 86 | upgrade, see \fBLIMITATIONS\fR. 87 | .TP 88 | .B install 89 | install is followed by one or more packages desired for installation or upgrading. 90 | All packages required by the package(s) specified for installation will also 91 | be retrieved and installed. 92 | The \fImirror\fR configuration is used to locate the desired packages. 93 | A specific distribution can be selected with the \fB\-t\fR option or the \fIdistname\fR configuration. 94 | .TP 95 | .B remove 96 | remove is identical to install except that packages are removed instead of 97 | installed (any configuration files are deleted too). 98 | It remove all files that are listed into the \fI/etc/setup/.lst.gz\fR 99 | file. 100 | Packages that the given \fIPACKAGE\fR depend on are not removed. 101 | Use with caution, there are small number of packages \fBcyg\-apt\fR will not 102 | remove, see \fBLIMITATIONS\fR. 103 | .TP 104 | .B purge 105 | purge is identical to remove except that packages are uninstalled and removed 106 | from the package cache. 107 | .TP 108 | .B source 109 | source causes \fBcyg\-apt\fR to fetch source packages. 110 | It will then find and download into the current directory the newest available 111 | version of that source package. 112 | While respect the distribution, set with the \fB\-t\fR option or the \fIdistname\fR configuration, if possible. 113 | .TP 114 | .B show 115 | show the \fIPACKAGE\fR description, as found in the setup.ini database. 116 | .TP 117 | .B search 118 | search performs a full text search on all available package lists for the 119 | \fISTRING\fR given. 120 | It searches the package names and the descriptions for an occurrence of the 121 | \fISTRING\fR and prints out the package name and the short description. 122 | Use the \fB\-s\fR option to treat \fISTRING\fR such as an POSIX regex pattern. 123 | .TP 124 | .B requires 125 | requires shows a listing of each dependency a \fIPACKAGE\fR has. 126 | .TP 127 | .B list 128 | list the installed packages, their versions and newer versions if available. 129 | .TP 130 | .B version 131 | version shows the installed version of a given package. If no packages is given, 132 | the versions of all packages are displayed. 133 | .TP 134 | .B new 135 | Show upgraded packages; print out a list of all packages that are to be upgraded. 136 | .TP 137 | .B filelist 138 | filelist is followed by a package to investigate: the files the package installs 139 | are listed. 140 | .TP 141 | .B find 142 | find is followed by a file to locate the package for: the package the file 143 | belongs to will be printed, if it can be found. 144 | This works for files installed directly from a tarball, but not for files created 145 | after the package is installed or by a postinstall script. 146 | .TP 147 | .B download 148 | download a given package to the package cache but do not install it. 149 | .TP 150 | .B ball 151 | ball shows the path to the tarball for the given package. 152 | .TP 153 | .B url 154 | url shows the URL for a given package's tarball. 155 | .TP 156 | .B md5 157 | md5 checks the md5 checksum of a package in the cache against the expected md5 158 | given in the setup.ini database. 159 | .IP 160 | Deprecated since version 1.2 and will be removed in 2.0, use checksum instead. 161 | .TP 162 | .B checksum 163 | Checks the digest of a package in the cache against the expected digest 164 | given in the setup.ini database. 165 | .TP 166 | .B postinstall 167 | Executes all undone postinstall scripts. 168 | .TP 169 | .B postremove 170 | Executes all undone preremove and postremove scripts. 171 | .SH OPTIONS 172 | .PP 173 | These options may be given on the command line. Most options are command specific. 174 | .TP 175 | .BR \-d ", " \-\-download\-only 176 | Download only; package files are only retrieved, not unpacked or installed. 177 | .TP 178 | .BR \-h ", " \-\-help 179 | Show a short usage summary. 180 | .TP 181 | .BR \-m ", " \-\-mirror = \fIURL 182 | Use the given download mirror. 183 | Be sure to give the complete URL. 184 | The correct \fIURL\fR will be a directory containing the server's \fIsetup.ini\fR. 185 | .TP 186 | .BR \-t ", " \-\-dist "=(" curr | test | prev ) 187 | Sets the distribution name and overwrite the \fIdistname\fR configuration. 188 | .TP 189 | .BR \-x ", " \-\-no\-deps 190 | ignore dependencies. 191 | .TP 192 | .BR \-s ", " \-\-regexp 193 | Treats the \fISTRING\fR operand as a POSIX regex pattern. 194 | .RS 195 | .PP 196 | Example: 197 | .RS 198 | .PP 199 | .nf 200 | $ cyg\-apt \-\-regexp search "p.thon" 201 | .fi 202 | .RE 203 | .RE 204 | .TP 205 | .BR \-f ", " \-\-force ", " \-\-nobarred 206 | add/remove packages cyg\-apt itself depends on. 207 | .IP 208 | \fBsetup\fR; overwriting ~/.cyg\-apt configuration file. 209 | .TP 210 | .BR \-y ", " \-\-nopostinstall 211 | do not run postinstall scripts when installing. 212 | .IP 213 | Deprecated since version 1.1 and will be removed in 2.0. 214 | .TP 215 | .BR \-y ", "\-\-nopostremove 216 | do not run preremove or postremove scripts when removing. 217 | .IP 218 | Deprecated since version 1.1 and will be removed in 2.0. 219 | .TP 220 | .BR \-q ", "\-\-quiet 221 | Quiet; Produces output suitable for logging, omitting progress indicators. 222 | .SH EXIT STATUS 223 | .PP 224 | The following exit values shall be returned: 225 | .TP 226 | 0 227 | All input files were output successfully. 228 | .TP 229 | >0 230 | An error occurred. 231 | .SH ENVIRONMENT 232 | .TP 233 | .I HOME 234 | This environment variable is used to find the configuration file \fI~/.cyg\-apt\fR. 235 | .TP 236 | .I USERPROFILE 237 | This environment variable is used to find the configuration file \fI~/.cyg\-apt\fR 238 | if \fIHOME\fR does not exist, such as on Windows. 239 | .SH FILES 240 | .TP 241 | .I ~/.cyg\-apt 242 | cyg\-apt is designed to be configured by this file alone. 243 | The syntax of the file is straightforward and there are additional comments to 244 | help you edit the file. 245 | The file lives in your home directory. 246 | For additional configurations, copy the file and place it in the current 247 | directory, then edit it: \fI./.cyg\-apt\fR is used over \fI~/.cyg\-apt\fR. 248 | .IP 249 | The key fields are: 250 | .RS 251 | .RS 252 | .TP 253 | .B ROOT 254 | The root of your Cygwin installation as a windows path ending with a slash. 255 | .TP 256 | .B mirror 257 | The url of your Cygwin mirror. 258 | .TP 259 | .B cache 260 | The path to your package cache. 261 | .TP 262 | .B always_update 263 | Always updates the package index files from the mirror. 264 | cyg\-apt will be faster and use less bandwidth if \fIFalse\fR but you will have 265 | to run the \fBupdate\fR command manually. 266 | .TP 267 | .B distname 268 | The target distribution name takes the following specific values; 269 | .RB ( prev | curr | test ). 270 | Usually you want the \fBcurr\fRent version of a package. 271 | .TP 272 | .B setup_ini 273 | Define the \fIsetup.ini\fR location, standard is 274 | .IR /etc/setup/setup.ini . 275 | .IP 276 | Deprecated since version 1.1 and will be removed in 2.0. 277 | .RE 278 | .RE 279 | .TP 280 | .I /etc/setup/setup.rc 281 | The official Cygwin setup program configuration. 282 | The \fBsetup\fR command reads this file to fetch the lastest mirror and cache. 283 | .TP 284 | .I /etc/setup/installed.db 285 | Fetch and push from the installed packages database. 286 | .TP 287 | .I /etc/postinstall/ 288 | Executes post install scripts 289 | .TP 290 | .I /etc/preremove/ 291 | Executes pre remove scripts 292 | .TP 293 | .I /etc/postremove/ 294 | Executes post remove scripts 295 | .SH SECURITY 296 | .PP 297 | \fBcyg\-apt\fR follows setup.exe in verifying downloaded setup.ini files using 298 | Cygwin's public key. 299 | This verification is performing using the Cygwin port of gpg package. 300 | This provides some assurance that the mirror is not providing malware versions 301 | of Cygwin packages, since any changes to setup.ini such as changes to package 302 | md5sum values will cause the signature not to match the file. 303 | An attacker able to edit cyg\-apt or replace gpg with their own package can subvert 304 | this protection. 305 | .SH BUGS 306 | .PP 307 | \fBcyg\-apt\fR, running within Cygwin, cannot alter packages it itself depends on. 308 | It is possible to run cyg\-apt in a Windows command shell, but a more convenient 309 | workaround is to use the standard setup.exe installer to update these packages. 310 | .PP 311 | Report bugs to the \fBcyg\-apt issue page\fR[1] where the development and maintenance 312 | is primarily done. 313 | .SH AUTHOR 314 | The original cyg\-apt was written by Jan Nieuwenhuizen. 315 | For a list of all authors, please see the \fBAUTHORS\fR[2] file. 316 | .SH NOTES 317 | .TP 318 | 1. cyg\-apt issue page 319 | https://github.com/nylen/cyg\-apt/issues 320 | .TP 321 | 2. AUTHORS 322 | /usr/share/doc/cyg\-apt/AUTHORS 323 | -------------------------------------------------------------------------------- /setup.hint: -------------------------------------------------------------------------------- 1 | category: Admin Utils 2 | requires: cygwin coreutils python python-argparse gnupg 3 | sdesc: "A Cygwin package manager" 4 | ldesc: "A Cygwin package manager. 5 | Allows you to install and remove packages on the command line, and provides other package management functions. It has a command syntax similar to apt-get and dpkg." 6 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | SRC = main.py 2 | D_BUILD = $(SD_BUILD)/$(ID_EXEC) 3 | EXEC = $(EXENAME) 4 | PYPKG = cygapt 5 | PY_VERSION_FILE = $(PYPKG)/version.py 6 | SRCLIB = $(wildcard $(PYPKG)/*.py) 7 | 8 | GPG_CYGWIN_PUBKEY = cygwin.sig 9 | 10 | all:: $(D_BUILD)/$(EXEC) 11 | 12 | $(D_BUILD): 13 | $(MKDIR) $(D_BUILD) 14 | $(MKDIR) $(SD_BUILD)/$(ID_SYSCONF)/postinstall 15 | $(MKDIR) $(SD_BUILD)/$(ID_DATA)/$(EXENAME) 16 | 17 | $(D_BUILD)/$(EXEC): $(SRC) $(SRCLIB) $(D_BUILD) 18 | $(SHELL_PATH) ./postinstall-gen.sh > $(SD_BUILD)/$(ID_SYSCONF)/postinstall/$(EXENAME).sh 19 | $(CP) $(SD_ROOT)/LICENSE $(PYPKG) 20 | @echo "__version__ = '$(VERSION)'" > $(PY_VERSION_FILE) 21 | $(PYTHON) setup.py install --root="../build" 22 | $(RM) $(PYPKG)/LICENSE 23 | $(CP) $(GPG_CYGWIN_PUBKEY) $(SD_BUILD)/$(ID_DATA)/$(EXENAME) 24 | $(CP) "$<" "$@" 25 | 26 | install: $(D_BUILD)/$(EXEC) 27 | $(INSTALL) -d -m 755 $(ID_ROOT)/$(ID_DATA)/$(EXENAME) 28 | $(INSTALL) $(GPG_CYGWIN_PUBKEY) $(ID_ROOT)/$(ID_DATA)/$(EXENAME) 29 | $(INSTALL) -m 755 "$<" $(ID_ROOT)/$(ID_EXEC) 30 | $(PYTHON) setup.py install 31 | $(INSTALL) $(SD_BUILD)/$(ID_SYSCONF)/postinstall/$(EXENAME).sh $(ID_ROOT)/$(ID_SYSCONF)/postinstall 32 | $(SHELL_PATH) $(ID_ROOT)/$(ID_SYSCONF)/postinstall/$(EXENAME).sh 33 | $(MV) $(ID_ROOT)/$(ID_SYSCONF)/postinstall/$(EXENAME).sh $(ID_ROOT)/$(ID_SYSCONF)/postinstall/$(EXENAME).sh.done 34 | 35 | clean: FORCE 36 | $(PYTHON) setup.py clean 37 | $(RM) -r build dist $(PY_VERSION_FILE) 38 | 39 | .PHONY: FORCE 40 | .EXPORT_ALL_VARIABLES: 41 | -------------------------------------------------------------------------------- /src/cygapt/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import cygapt.version as version; 17 | 18 | __version__ = version.__version__; 19 | -------------------------------------------------------------------------------- /src/cygapt/argparser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import argparse; 17 | import warnings; 18 | 19 | class CygAptArgParser(): 20 | def __init__(self, scriptname=None): 21 | self.__parser = None; 22 | self.__isCompiled = False; 23 | 24 | self.setAppName(scriptname); 25 | 26 | self.__parser = argparse.ArgumentParser( 27 | prog=self._appName, 28 | add_help=False, 29 | ); 30 | 31 | self.__commands = [ 32 | 'setup', 33 | 'update', 34 | 'ball', 35 | 'download', 36 | 'filelist', 37 | 'find', 38 | 'help', 39 | 'install', 40 | 'list', 41 | 'md5', 42 | 'checksum', 43 | 'missing', 44 | 'new', 45 | 'purge', 46 | 'remove', 47 | 'requires', 48 | 'search', 49 | 'show', 50 | 'source', 51 | 'upgrade', 52 | 'url', 53 | 'version', 54 | 'postinstall', 55 | 'postremove', 56 | ]; 57 | 58 | def getAppName(self): 59 | return self._appName; 60 | 61 | def setAppName(self, app_name): 62 | self._appName = str(app_name); 63 | 64 | def compile(self): 65 | if self.__isCompiled: 66 | return; 67 | 68 | self.__parser.add_argument( 69 | 'command', 70 | nargs='?', 71 | default="help", 72 | choices=self.__commands 73 | ); 74 | 75 | self.__parser.add_argument( 76 | 'package', 77 | nargs='*' 78 | ); 79 | 80 | self.__parser.add_argument( 81 | '-q', '--quiet', 82 | action='store_false', 83 | default=True, 84 | help="Loggable output - no progress indicator", 85 | dest='verbose' 86 | ); 87 | 88 | self.__parser.add_argument( 89 | '-d', '--download', 90 | action='store_true', 91 | dest='download_p', 92 | help="download only" 93 | ); 94 | 95 | self.__parser.add_argument( 96 | '-m', '--mirror', 97 | nargs=1, 98 | help="use mirror" 99 | ); 100 | 101 | self.__parser.add_argument( 102 | '-t', '--dist', 103 | nargs=1, 104 | dest='distname', 105 | default='curr', 106 | choices=['curr', 'test', 'prev'], 107 | help="set dist name" 108 | ); 109 | 110 | self.__parser.add_argument( 111 | '-a', 112 | action='store_true', 113 | dest='noupdate', 114 | help="do not update" 115 | ); 116 | 117 | self.__parser.add_argument( 118 | '-x', '--no-deps', 119 | action='store_true', 120 | dest='nodeps_p', 121 | help="ignore dependencies" 122 | ); 123 | 124 | self.__parser.add_argument( 125 | '-s', '--regexp', 126 | action='store_true', 127 | dest='regex_search', 128 | help="search as regex pattern" 129 | ); 130 | 131 | self.__parser.add_argument( 132 | '-f', '--nobarred', '--force', 133 | action='store_true', 134 | dest='force', 135 | help="add/remove packages cyg-apt depends on" 136 | ); 137 | 138 | self.__parser.add_argument( 139 | '-X', '--no-verify', 140 | action='store_false', 141 | dest='verify', 142 | help="do not verify setup.ini signatures" 143 | ); 144 | 145 | self.__parser.add_argument( 146 | '-y', '--nopostinstall', 147 | action='store_true', 148 | help="do not run postinstall scripts" 149 | ); 150 | 151 | self.__parser.add_argument( 152 | '-z', '--nopostremove', 153 | action='store_true', 154 | help="do not run preremove/postremove scripts" 155 | ); 156 | 157 | self.__parser.add_argument( 158 | '-h', '--help', 159 | action='store_true', 160 | help="show brief usage" 161 | ); 162 | 163 | self.__isCompiled = True; 164 | 165 | 166 | def parse(self): 167 | """Parse the system args 168 | 169 | """ 170 | if not self.__isCompiled: 171 | self.compile(); 172 | 173 | args = self.__parser.parse_args(); 174 | 175 | args = self.__castTypes(args); 176 | 177 | if args.nopostinstall : 178 | warnings.warn( 179 | "The option -y, --nopostinstall is deprecated since version " 180 | "1.1 and will be removed in 2.0.", 181 | DeprecationWarning 182 | ); 183 | 184 | if args.nopostremove : 185 | warnings.warn( 186 | "The option -z, --nopostremove is deprecated since version " 187 | "1.1 and will be removed in 2.0.", 188 | DeprecationWarning 189 | ); 190 | 191 | if 'md5' == args.command : 192 | args.command = 'checksum'; 193 | warnings.warn( 194 | "The command md5 is deprecated since version 1.2 and will be " 195 | "removed in 2.0, use checksum instead.", 196 | DeprecationWarning 197 | ); 198 | 199 | return args; 200 | 201 | def __castTypes(self, args): 202 | if args.mirror and isinstance(args.mirror, list): 203 | args.mirror = args.mirror[0]; 204 | 205 | if args.distname and isinstance(args.distname, list): 206 | args.distname = args.distname[0]; 207 | 208 | return args; 209 | -------------------------------------------------------------------------------- /src/cygapt/copying.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | help_message = ( 15 | "Copyright (C) 2002-2009 Jan Nieuwenhuizen {LF}" 16 | " 2002-2009 Chris Cormie {LF}" 17 | " 2012 James Nylen {LF}" 18 | " 2012-2014 Alexandre Quercia {LF}" 19 | "License: GNU GPL version 3 {LF}" 20 | "This is free software: you are free to change and redistribute it.{LF}" 21 | "There is NO WARRANTY, to the extent permitted by law.{LF}" 22 | "".format(LF="\n") 23 | ); 24 | -------------------------------------------------------------------------------- /src/cygapt/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | class ApplicationException(Exception): 17 | def __init__(self, message="", code=1, previous=None): 18 | Exception.__init__(self, message); 19 | self._message = str(message); 20 | self._code = int(code); 21 | if previous is None: 22 | self._previous = None; 23 | else: 24 | assert isinstance(previous, BaseException); 25 | self._previous = previous; 26 | 27 | def getCode(self): 28 | return self._code; 29 | 30 | def getMessage(self): 31 | return self._message; 32 | 33 | def getPrevious(self): 34 | return self._previous; 35 | 36 | class InvalidArgumentException(ApplicationException): 37 | pass; 38 | 39 | class PathExistsException(ApplicationException): 40 | pass; 41 | 42 | class InvalidFileException(ApplicationException): 43 | pass; 44 | 45 | class UnexpectedValueException(ApplicationException): 46 | pass; 47 | -------------------------------------------------------------------------------- /src/cygapt/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import print_function; 16 | from __future__ import absolute_import; 17 | 18 | import sys; 19 | import os; 20 | 21 | import cygapt.utils as cautils; 22 | from cygapt.setup import CygAptSetup; 23 | from cygapt.argparser import CygAptArgParser; 24 | from cygapt.cygapt import CygApt; 25 | from cygapt.exception import ApplicationException; 26 | from cygapt.path_mapper import PathMapper; 27 | 28 | class CygAptMain(): 29 | def __init__(self): 30 | self.__appName = None; 31 | try: 32 | exit_code = self.main(); 33 | except ApplicationException as e: 34 | print("{0}: {1}".format(self.getAppName(), e), 35 | file=sys.stderr 36 | ); 37 | exit_code = e.getCode(); 38 | 39 | sys.exit(exit_code); 40 | 41 | def getAppName(self): 42 | if self.__appName is None: 43 | self.__appName = os.path.basename(sys.argv[0]); 44 | if (self.__appName[-3:] == ".py"): 45 | self.__appName = self.__appName[:-3]; 46 | return self.__appName; 47 | 48 | def main(self): 49 | # parse command line arguments 50 | cap = CygAptArgParser(scriptname=self.getAppName()); 51 | args = cap.parse(); 52 | 53 | # initialize main variables with command line arguments and options 54 | main_command = args.command; 55 | main_files = args.package[:]; 56 | main_files.insert(0, main_command); 57 | main_packagename = None; 58 | if len(args.package) > 0: 59 | main_packagename = args.package[0]; 60 | main_verbose = args.verbose; 61 | main_download_p = args.download_p; 62 | main_mirror = args.mirror; 63 | main_distname = args.distname; 64 | main_noupdate = args.noupdate; 65 | main_nodeps_p = args.nodeps_p; 66 | main_regex_search = args.regex_search; 67 | main_nobarred = args.force; 68 | main_verify = args.verify; 69 | main_nopostinstall = args.nopostinstall; 70 | main_nopostremove = args.nopostremove; 71 | main_downloads = None; 72 | main_dists = 0; 73 | main_installed = 0; 74 | 75 | # locate and parse the configuration file 76 | main_cyg_apt_rc = self.getConfigPath(); 77 | 78 | config = None; 79 | if main_cyg_apt_rc: 80 | config = cautils.parse_rc(main_cyg_apt_rc); 81 | elif (main_command != "setup"): 82 | print( 83 | "{0}: no .{0}: run \"{0} setup\"".format(self.getAppName()), 84 | file=sys.stderr 85 | ); 86 | return 1; 87 | 88 | # create a CygAptSetup instance and its dependencies 89 | main_cygwin_p = (sys.platform == "cygwin"); 90 | 91 | is_64_bit = False; 92 | if main_cygwin_p : 93 | # Running Cygwin python, so python architecture == Cygwin architecture 94 | if 2**32 < sys.maxsize : 95 | is_64_bit = True; 96 | elif config and main_command != 'setup' : 97 | # Running Windows python, so examine cygwin1.dll 98 | pathMapper = PathMapper(config.ROOT.rstrip('\\/'), main_cygwin_p); 99 | if cautils.pe_is_64_bit(pathMapper.mapPath("/bin/cygwin1.dll")) : 100 | is_64_bit = True; 101 | 102 | if is_64_bit : 103 | main_arch = 'x86_64'; 104 | else: 105 | main_arch = 'x86'; 106 | 107 | cas = CygAptSetup(main_cygwin_p, main_verbose, main_arch); 108 | 109 | # run command 110 | if (main_command == "setup"): 111 | cas.setup(args.force); 112 | return 0; 113 | elif (main_command == "help"): 114 | cas.usage(main_cyg_apt_rc); 115 | return 0; 116 | elif (main_command == "update"): 117 | cas.update(main_cyg_apt_rc, main_verify, main_mirror=main_mirror); 118 | return 0; 119 | 120 | # make an update if needed 121 | update_not_needed = [ 122 | "ball", "find", "help", "purge", "remove", "version", 123 | "filelist", "update", "setup", "md5", 124 | ]; 125 | always_update = config.always_update; 126 | always_update = always_update and\ 127 | main_command not in update_not_needed and\ 128 | not main_noupdate; 129 | if always_update: 130 | cas.update(main_cyg_apt_rc, main_verify, main_mirror=main_mirror); 131 | 132 | if main_command and main_command in dir(CygApt): 133 | cyg_apt = CygApt( 134 | main_packagename, 135 | main_files, 136 | main_cyg_apt_rc, 137 | main_cygwin_p, 138 | main_download_p, 139 | main_mirror, 140 | main_downloads, 141 | main_distname, 142 | main_nodeps_p, 143 | main_regex_search, 144 | main_nobarred, 145 | main_nopostinstall, 146 | main_nopostremove, 147 | main_dists, 148 | main_installed, 149 | self.getAppName(), 150 | main_verbose, 151 | main_arch, 152 | ); 153 | 154 | getattr(cyg_apt, main_command)(); 155 | else: 156 | cas.usage(main_cyg_apt_rc); 157 | 158 | return 0; 159 | 160 | def getConfigPath(self): 161 | main_cyg_apt_rc = None; 162 | 163 | # Take most of our configuration from .cyg-apt 164 | # preferring .cyg-apt in current directory over $(HOME)/.cyg-apt 165 | cwd_cyg_apt_rc = os.path.join( 166 | os.getcwd(), 167 | ".{0}".format(self.getAppName()) 168 | ); 169 | if os.path.exists(cwd_cyg_apt_rc): 170 | main_cyg_apt_rc = cwd_cyg_apt_rc; 171 | elif "HOME" in os.environ: 172 | home_cyg_apt_rc = os.path.join( 173 | os.environ['HOME'], 174 | ".{0}".format(self.getAppName()) 175 | ); 176 | if os.path.exists(home_cyg_apt_rc): 177 | main_cyg_apt_rc = home_cyg_apt_rc; 178 | elif "USERPROFILE" in os.environ : 179 | home_cyg_apt_rc = os.path.join( 180 | os.environ['USERPROFILE'], 181 | ".{0}".format(self.getAppName()) 182 | ); 183 | if os.path.exists(home_cyg_apt_rc) : 184 | main_cyg_apt_rc = home_cyg_apt_rc; 185 | 186 | if main_cyg_apt_rc: 187 | # Take our configuration from .cyg-apt 188 | # Command line options can override, but only for this run. 189 | main_cyg_apt_rc = main_cyg_apt_rc.replace("\\","/"); 190 | 191 | return main_cyg_apt_rc; 192 | 193 | if __name__ == '__main__': 194 | CygAptMain(); 195 | -------------------------------------------------------------------------------- /src/cygapt/ob.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import sys; 17 | from cStringIO import StringIO; 18 | 19 | class CygAptOb: 20 | """Output Buffering Control (like php) 21 | 22 | The Output Control functions allow you to control 23 | when output is sent from the script. 24 | """ 25 | 26 | def __init__(self, start=False): 27 | self._state = False; 28 | self._buffer = None; 29 | self._value = ''; 30 | self._stdout = None; 31 | if start: 32 | self.start(); 33 | 34 | def start(self): 35 | """Turn on output buffering 36 | """ 37 | self._stdout = sys.stdout; 38 | sys.stdout = StringIO(); 39 | self._buffer = sys.stdout; 40 | self._state = True; 41 | self._value = ''; 42 | 43 | def _end(self): 44 | """Turn off output buffering 45 | """ 46 | if self._state: 47 | self._buffer.close(); 48 | self._buffer = None; 49 | sys.stdout = self._stdout; 50 | self._state = False; 51 | 52 | def flush(self): 53 | """Flush (send) the output buffer 54 | """ 55 | self.clean(); 56 | if self._value: 57 | self._stdout.write(self._value); 58 | self._stdout.flush(); 59 | self._value = ''; 60 | 61 | def getFlush(self): 62 | """Flush the output buffer, 63 | return it as a string and turn off output buffering 64 | """ 65 | self.clean(); 66 | content = self.getContents(); 67 | self._end(); 68 | return content; 69 | 70 | def endFlush(self): 71 | """Flush (send) the output buffer and turn off output buffering 72 | """ 73 | self.flush(); 74 | self._end(); 75 | 76 | def clean(self): 77 | """Clean (erase) the output buffer 78 | """ 79 | if self._state: 80 | self._value = self._buffer.getvalue(); 81 | self._buffer.truncate(0); 82 | 83 | def getClean(self): 84 | """Get current buffer contents and delete current output buffer 85 | """ 86 | if not self._state: 87 | return False; 88 | 89 | content = self.getContents(); 90 | self.endClean(); 91 | return content; 92 | 93 | def endClean(self): 94 | """Clean (erase) the output buffer and turn off output buffering 95 | """ 96 | self._value = ''; 97 | self._end(); 98 | 99 | def implicitFlush(self, flag=True): 100 | """Turn implicit flush on/off 101 | """ 102 | if flag and self._state: 103 | self._end(); 104 | elif not flag and not self._state: 105 | self.start(); 106 | 107 | def getContents(self): 108 | """Return the contents of the output buffer 109 | """ 110 | if not self._state: 111 | return False; 112 | 113 | buf = self._buffer.getvalue(); 114 | if buf: 115 | self._value = buf; 116 | return self._value; 117 | 118 | def getLength(self): 119 | """Return the length of the output buffer 120 | """ 121 | if not self._state: 122 | return False; 123 | 124 | return len(self.getContents()); 125 | -------------------------------------------------------------------------------- /src/cygapt/path_mapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import cygapt.utils as cautils; 17 | from cygapt.process import Process; 18 | 19 | class PathMapper: 20 | def __init__(self, root, cygwin_p): 21 | self.__root = root; 22 | self.__mountRoot = "/"; 23 | self.__cygwinPlatform = cygwin_p; 24 | self.__map = {}; 25 | 26 | p = Process([self.__root + "/bin/mount"]); 27 | p.run(); 28 | mountout = p.getOutput().splitlines(True); 29 | self._addMapping(mountout); 30 | 31 | def getRoot(self): 32 | return self.__root; 33 | 34 | def setRoot(self, root_dir): 35 | self.__root = root_dir; 36 | 37 | def getMountRoot(self): 38 | return self.__mountRoot; 39 | 40 | def setMountRoot(self, mount_root): 41 | self.__mountRoot = mount_root; 42 | 43 | def getMap(self): 44 | return self.__map; 45 | 46 | def setMap(self, mapping): 47 | self.__map = mapping; 48 | 49 | def _addMapping(self, mtab): 50 | self.__map = {}; 51 | mtab = [l.split() for l in mtab]; 52 | for l in mtab: 53 | if l[2] != "/": 54 | self.__map[l[2] + "/"] = l[0] + "/"; 55 | else: 56 | self.__mountRoot = l[0] + "/"; 57 | 58 | def mapPath(self, path): 59 | if self.__cygwinPlatform: 60 | return path; 61 | 62 | # does map a path that has been already mapped 63 | if ':' in path : 64 | return path; 65 | 66 | # sort to map to /e/bar/foo in pefrence /e/bar 67 | l = cautils.prsort(list(self.__map.keys())); 68 | for cygpath in l: 69 | index = path.find(cygpath); 70 | if index == 0: 71 | return self.__map[cygpath]+path[len(cygpath):]; 72 | if cygpath.rstrip('/') == path : 73 | return self.__map[cygpath].rstrip('/'); 74 | return self.__root + path; 75 | -------------------------------------------------------------------------------- /src/cygapt/process/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | 17 | import subprocess; 18 | import shlex; 19 | 20 | from .exception import ProcessFailedException; 21 | from .exception import LogicException; 22 | 23 | class Process: 24 | """Process is a thin wrapper around subprocess.* functions. 25 | 26 | @author: alquerci 27 | """ 28 | 29 | exitCodes = { 30 | 0: 'OK', 31 | 1: 'General error', 32 | 2: 'Misuse of shell builtins', 33 | 34 | # User-defined errors must use exit codes in the 64-113 range. 35 | 36 | 126: 'Invoked command cannot execute', 37 | 127: 'Command not found', 38 | 128: 'Invalid exit argument', 39 | 40 | # signals 41 | 129: 'Hangup', 42 | 130: 'Interrupt', 43 | 131: 'Quit and dump core', 44 | 132: 'Illegal instruction', 45 | 133: 'Trace/breakpoint trap', 46 | 134: 'Process aborted', 47 | 135: 'Bus error: "access to undefined portion of memory object"', 48 | 136: 'Floating point exception: "erroneous arithmetic operation"', 49 | 137: 'Kill (terminate immediately)', 50 | 138: 'User-defined 1', 51 | 139: 'Segmentation violation', 52 | 140: 'User-defined 2', 53 | 141: 'Write to pipe with no one reading', 54 | 142: 'Signal raised by alarm', 55 | 143: 'Termination (request to terminate)', 56 | # 144 - not defined 57 | 145: 'Child process terminated, stopped (or continued*)', 58 | 146: 'Continue if stopped', 59 | 147: 'Stop executing temporarily', 60 | 148: 'Terminal stop signal', 61 | 149: 'Background process attempting to read from tty ("in")', 62 | 150: 'Background process attempting to write to tty ("out")', 63 | 151: 'Urgent data available on socket', 64 | 152: 'CPU time limit exceeded', 65 | 153: 'File size limit exceeded', 66 | 154: 'Signal raised by timer counting virtual time: "virtual timer expired"', 67 | 155: 'Profiling timer expired', 68 | # 156 - not defined 69 | 157: 'Pollable event', 70 | # 158 - not defined 71 | 159: 'Bad syscall', 72 | }; 73 | 74 | def __init__(self, commandLine, cwd=None): 75 | """Constructor. 76 | 77 | @param commandLine: str|list The command line to run 78 | @param cwd: str|None The working directory or None to use the working 79 | directory of the current Python process 80 | """ 81 | self.__exitCode = None; 82 | 83 | self.setCommandLine(commandLine); 84 | self.setWorkingDirectory(cwd); 85 | self.setInput(None); 86 | 87 | def run(self, inheritOutput=False): 88 | """Runs the process. 89 | 90 | The STDOUT and STDERR are also available after the process is finished 91 | via the getOutput() and getErrorOutput() methods. 92 | 93 | @param inheritOutput: bool Whether to inherit the STDOUT and STDERR or not 94 | 95 | @return: int The exit status code 96 | """ 97 | self.__stdout = ''; 98 | self.__stderr = ''; 99 | 100 | commandLine = self.getCommandLine(); 101 | 102 | if isinstance(commandLine, str) : 103 | commandLine = shlex.split(commandLine); 104 | 105 | outputHandlers = subprocess.PIPE; 106 | if inheritOutput : 107 | outputHandlers = None; 108 | 109 | inputHandler = subprocess.PIPE; 110 | inputString = None; 111 | stdin = self.getInput(); 112 | if self.__isFileLike(stdin) : 113 | inputHandler = stdin; 114 | else: 115 | inputString = stdin; 116 | 117 | try: 118 | process = subprocess.Popen( 119 | commandLine, 120 | stdin=inputHandler, 121 | stdout=outputHandlers, 122 | stderr=outputHandlers, 123 | cwd=self.getWorkingDirectory(), 124 | ); 125 | 126 | stdout, stderr = process.communicate(inputString); 127 | self.__exitCode = process.returncode; 128 | self.__stdout = stdout; 129 | self.__stderr = stderr; 130 | except OSError as e: 131 | self.__exitCode = e.errno; 132 | 133 | return self.__exitCode; 134 | 135 | def mustRun(self, inheritOutput=False): 136 | """Runs and terminate successfully the process. 137 | 138 | This is identical to run() except that an exception is raised if the process 139 | exits with a non-zero exit code. 140 | 141 | @param inheritOutput: bool Whether to inherit the STDOUT and STDERR or not 142 | 143 | @raise ProcessFailedException: if the process didn't terminate successfully 144 | """ 145 | self.run(inheritOutput); 146 | 147 | if 0 != self.getExitCode() : 148 | raise ProcessFailedException(self); 149 | 150 | def setInput(self, stdin): 151 | """Sets the input. 152 | 153 | This content will be passed to the underlying process standard input. 154 | 155 | @param stdin: str|file-like The content 156 | """ 157 | if None is not stdin and not self.__isFileLike(stdin) : 158 | if not isinstance(stdin, (str, float, int, bool)) : 159 | raise TypeError('Process.setInput only accepts strings or file-like objects.'); 160 | 161 | stdin = str(stdin); 162 | 163 | self.__stdin = stdin; 164 | 165 | def getInput(self): 166 | """Gets the Process input. 167 | 168 | @return: str|file-like|None The Process input 169 | """ 170 | return self.__stdin; 171 | 172 | def getOutput(self): 173 | """Returns the current output of the process (STDOUT). 174 | 175 | @return: str The process output 176 | 177 | @raise LogicException: In case the process is not started 178 | """ 179 | if None is self.getExitCode() : 180 | raise LogicException('Process must be started before calling getOutput'); 181 | 182 | return self.__stdout; 183 | 184 | def getErrorOutput(self): 185 | """Returns the current error output of the process (STDERR). 186 | 187 | @return: str The process error output 188 | 189 | @raise LogicException: In case the process is not started 190 | """ 191 | if None is self.getExitCode() : 192 | raise LogicException('Process must be started before calling getErrorOutput'); 193 | 194 | return self.__stderr; 195 | 196 | def getExitCode(self): 197 | """Returns the exit code returned by the process. 198 | 199 | @return: int|None The exit status code, None if the Process is not terminated 200 | """ 201 | return self.__exitCode; 202 | 203 | def getExitCodeText(self): 204 | """Returns a string representation for the exit code returned by the process. 205 | 206 | This method relies on the Unix exit code status standardization 207 | and might not be relevant for other operating systems. 208 | 209 | @return: None|str A string representation for the exit status code, None if the Process is not terminated. 210 | 211 | @see: http://tldp.org/LDP/abs/html/exitcodes.html 212 | @see: http://en.wikipedia.org/wiki/Unix_signal 213 | """ 214 | exitCode = self.getExitCode(); 215 | 216 | if None is exitCode : 217 | return; 218 | 219 | try: 220 | return self.exitCodes[exitCode]; 221 | except KeyError: 222 | pass; 223 | 224 | return 'Unknown error'; 225 | 226 | def setCommandLine(self, commandLine): 227 | """Sets the command line to be executed. 228 | 229 | @param commandLine: list|str The command to execute 230 | """ 231 | self.__commandLine = commandLine; 232 | 233 | def getCommandLine(self): 234 | """Gets the command line to be executed. 235 | 236 | @return: str|list The command to execute 237 | """ 238 | return self.__commandLine[:]; 239 | 240 | def setWorkingDirectory(self, workingDirectory): 241 | """Sets the current working directory. 242 | 243 | @param workingDirectory: str|None The new working directory or None to use 244 | the working directory of the current 245 | Python process 246 | """ 247 | self.__workingDirectory = workingDirectory; 248 | 249 | def getWorkingDirectory(self): 250 | """Gets the working directory. 251 | 252 | @return: str|None The working directory or None to use the working 253 | directory of the current Python process 254 | """ 255 | if None is self.__workingDirectory : 256 | return; 257 | 258 | return self.__workingDirectory[:]; 259 | 260 | def __isFileLike(self, value): 261 | try: 262 | value.fileno(); 263 | except: 264 | return False; 265 | else: 266 | return True; 267 | -------------------------------------------------------------------------------- /src/cygapt/process/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | class ProcessFailedException(Exception): 17 | def __init__(self, process): 18 | Exception.__init__(self, """\ 19 | The command "{0}" failed. 20 | Exit Code: {1} ({2}) 21 | 22 | Output: 23 | ================ 24 | {3} 25 | 26 | Error Output: 27 | ================ 28 | {4}\ 29 | """.format( 30 | process.getCommandLine(), 31 | process.getExitCode(), 32 | process.getExitCodeText(), 33 | process.getOutput(), 34 | process.getErrorOutput(), 35 | )); 36 | 37 | class LogicException(Exception): 38 | pass; 39 | -------------------------------------------------------------------------------- /src/cygapt/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import print_function; 15 | from __future__ import absolute_import; 16 | 17 | import bz2; 18 | import inspect; 19 | import os; 20 | import shutil; 21 | import sys; 22 | import urllib; 23 | import platform; 24 | 25 | from cygapt.cygapt import CygApt; 26 | from cygapt.exception import ApplicationException; 27 | from cygapt.exception import PathExistsException; 28 | from cygapt.exception import UnexpectedValueException; 29 | from cygapt.utils import RequestException; 30 | from cygapt.path_mapper import PathMapper; 31 | import cygapt.utils as cautils; 32 | import cygapt.version as version; 33 | import cygapt.copying as copying; 34 | from cygapt.structure import ConfigStructure; 35 | from cygapt.process import Process; 36 | 37 | class CygAptSetup: 38 | RC_OPTIONS = [ 39 | 'ROOT', 40 | 'mirror', 41 | 'cache', 42 | 43 | # BC layer for `setup_ini` configuration field 44 | 'setup_ini', 45 | 46 | 'distname', 47 | 'barred', 48 | 'always_update' 49 | ]; 50 | RC_COMMENTS = { 51 | 'ROOT' : "# The root of your Cygwin installation as a windows path\n", 52 | 'mirror' : ( 53 | "# URL of your Cygwin mirror: example " 54 | "http://mirror.internode.on.net/pub/cygwin/\n" 55 | ), 56 | 'cache' : ( 57 | "# Your package cache as a POSIX path: example " 58 | "/e/home/cygwin_package_cache\n" 59 | ), 60 | 61 | # BC layer for `setup_ini` configuration field 62 | "setup_ini" : ( 63 | "# setup.ini lists available packages and is " 64 | "downloaded from the top level\n" 65 | "# of the downloaded mirror. Standard location is " 66 | "/etc/setup/setup.ini,\n" 67 | "# seutp-2.ini for Cygwin 1.7 Beta\n" 68 | "# Deprecated since version 1.1 and will be removed in 2.0.\n" 69 | "# " 70 | ), 71 | 72 | "distname" : ( 73 | "# The distribution, current previous or test " 74 | "[curr, prev, test].\n" 75 | "# Usually you want the \"curr\" version of a package.\n" 76 | ), 77 | "barred" : ( 78 | "# Packages which cyg-apt can't change under Cygwin " 79 | "since it depends on them.\n" 80 | "# Run cyg-apt under DOS with -f (force) option to change " 81 | "these packages.\n" 82 | "# Treat Cygwin core packages with CAUTION.\n" 83 | ), 84 | "always_update" : ( 85 | "# Always update setup.ini before any command " 86 | "that uses it. cyg-apt will be\n# faster and use less bandwidth if " 87 | "False but you will have to run the update\n# command manually.\n" 88 | ), 89 | }; 90 | GPG_GOOD_FINGER = "1169 DF9F 2273 4F74 3AA5 9232 A9A2 62FF 6760 41BA"; 91 | GPG_CYG_PUBLIC_RING_URI = "http://cygwin.com/key/pubring.asc"; 92 | VERSION = version.__version__; 93 | 94 | def __init__(self, cygwin_p, verbose, arch="x86"): 95 | self.__cygwinPlatform = cygwin_p; 96 | self.__verbose = verbose; 97 | self.__appName = os.path.basename(sys.argv[0]); 98 | self.setTmpDir(); 99 | self.setPathMapper(); 100 | self.__setupDir = "/etc/setup"; 101 | self.__rc = ConfigStructure(); 102 | self.__arch = arch; 103 | 104 | self.__rc.ROOT = self.__pm.getMountRoot(); 105 | 106 | def getCygwinPlatform(self): 107 | return self.__cygwinPlatform; 108 | 109 | def setCygwinPlatform(self, cygwin_p): 110 | self.__cygwinPlatform = bool(cygwin_p); 111 | 112 | def setVerbose(self, verbose): 113 | self.__verbose = verbose; 114 | 115 | def getVerbose(self): 116 | return self.__verbose; 117 | 118 | def getAppName(self): 119 | return self.__appName; 120 | 121 | def setAppName(self, app_name): 122 | self.__appName = str(app_name); 123 | 124 | def getTmpDir(self): 125 | return self.__tmpDir; 126 | 127 | def setTmpDir(self, tmp_dir=None): 128 | if tmp_dir: 129 | self.__tmpDir = tmp_dir; 130 | elif 'TMP' in os.environ: 131 | self.__tmpDir = os.environ['TMP']; 132 | else: 133 | self.__tmpDir = "/usr/share/cyg-apt"; 134 | 135 | def getPathMapper(self): 136 | return self.__pm; 137 | 138 | def setPathMapper(self, path_mapper=None): 139 | if path_mapper: 140 | assert isinstance(path_mapper, PathMapper); 141 | self.__pm = path_mapper; 142 | else: 143 | self.__pm = PathMapper("", self.__cygwinPlatform); 144 | 145 | def getRC(self): 146 | return self.__rc; 147 | 148 | def setRC(self, rc_structure): 149 | assert isinstance(rc_structure, ConfigStructure); 150 | self.__rc = rc_structure; 151 | 152 | def getSetupDir(self): 153 | return self.__setupDir; 154 | 155 | def setSetupDir(self, setup_dir): 156 | self.__setupDir = str(setup_dir); 157 | 158 | def setArchitecture(self, architecture): 159 | self.__arch = architecture; 160 | 161 | def _cygwinVersion(self): 162 | return float(platform.release()[:3]); 163 | 164 | def getSetupRc(self, location): 165 | filename = os.path.join(location, "setup.rc"); 166 | if not (os.path.exists(filename)): 167 | return (None, None); 168 | f = open(filename); 169 | setup_rc = f.readlines(); 170 | f.close(); 171 | last_cache = None; 172 | last_mirror = None; 173 | for i in range(0, (len(setup_rc) -1)): 174 | if 'last-cache' in setup_rc[i]: 175 | last_cache = setup_rc[i+1].strip(); 176 | if 'last-mirror' in setup_rc[i]: 177 | last_mirror = setup_rc[i+1].strip(); 178 | last_cache = self.__pm.mapPath(last_cache); 179 | return (last_cache, last_mirror); 180 | 181 | def setup(self, force=False): 182 | """create cyg-apt configuration file, it overwrite with -f option""" 183 | if not self.__cygwinPlatform: 184 | msg = "setup outside Cygwin not supported."; 185 | raise PlatformException(msg); 186 | if "HOME" in os.environ: 187 | rc_file = os.path.join( 188 | os.environ['HOME'], 189 | ".{0}".format(self.__appName) 190 | ); 191 | else: 192 | msg = "Can't locate home directory. Setup failed."; 193 | raise EnvironementException(msg); 194 | if os.path.exists(rc_file) and not force: 195 | msg = "{0} exists, not overwriting.".format(rc_file); 196 | raise PathExistsException(msg, code=0); 197 | 198 | installed_db = os.path.join(self.__setupDir, "installed.db"); 199 | missing_cache_marker = ""; 200 | missing_mirror_marker = ""; 201 | self.__rc.distname = 'curr'; 202 | # Refuse to remove/install any package including these substrings 203 | # since cyg-apt is dependent on them 204 | self.__rc.barred = ""; 205 | self.__rc.always_update = False; 206 | 207 | if not self.__cygwinPlatform: 208 | msg = "Setup only supported under Cygwin."; 209 | raise PlatformException(msg); 210 | 211 | (last_cache, last_mirror) = self.getSetupRc(self.__setupDir); 212 | if ((not last_cache) or (not last_mirror)): 213 | (last_cache, last_mirror) = self._getPre17Last(self.__setupDir); 214 | if ((not last_cache) or (not last_mirror)): 215 | print("{0}: {1}/setup.rc not found. Please edit {2} to "\ 216 | "provide mirror and cache. See cygwin.com/mirrors.html "\ 217 | "for the list of mirrors.".format( 218 | self.__appName, 219 | self.__setupDir, 220 | rc_file 221 | )); 222 | last_cache = missing_cache_marker; 223 | last_mirror = missing_mirror_marker; 224 | self.__rc.mirror = last_mirror; 225 | self.__rc.cache = last_cache; 226 | 227 | # BC layer for `setup_ini` configuration field 228 | self.__rc.setup_ini = "{0}/setup.ini".format(self.__setupDir); 229 | 230 | contents = ""; 231 | for i in self.__rc.__dict__: 232 | if i in list(self.RC_COMMENTS.keys()): 233 | contents += self.RC_COMMENTS[i]; 234 | contents += "{0}=\"{1}\"\n\n".format(i, self.__rc.__dict__[i]); 235 | f = open(rc_file, 'w'); 236 | f.write(contents); 237 | f.close(); 238 | print("{0}: creating {1}".format(self.__appName, rc_file)); 239 | 240 | if not os.path.isdir(self.__rc.ROOT): 241 | msg = "{0} no root directory".format(self.__rc.ROOT); 242 | raise UnexpectedValueException(msg); 243 | if not os.path.isdir(self.__setupDir): 244 | sys.stderr.write('creating {0}\n'.format(self.__setupDir)); 245 | os.makedirs(self.__setupDir); 246 | if not os.path.isfile(installed_db): 247 | self._writeInstalled(installed_db); 248 | 249 | setupIniPath = os.path.join( 250 | self.__pm.mapPath(self.__rc.cache), 251 | urllib.quote(self.__rc.mirror+('' if self.__rc.mirror.endswith('/') else '/'), '').lower(), 252 | self.__arch, 253 | 'setup.ini', 254 | ); 255 | if not os.path.isfile(setupIniPath): 256 | sys.stderr.write('getting {0}\n'.format(setupIniPath)); 257 | self.update(rc_file, True); 258 | 259 | def usage(self, cyg_apt_rc=None): 260 | print("{0}, version {1}".format(self.__appName, self.VERSION)); 261 | print(copying.help_message, end="\n\n"); 262 | if (cyg_apt_rc): 263 | print("Configuration: {0}".format(cyg_apt_rc)); 264 | print("Usage: {0} [OPTION]... COMMAND [PACKAGE]...".format( 265 | self.__appName 266 | )); 267 | print("\n Commands:"); 268 | members = []; 269 | for m in inspect.getmembers(CygAptSetup) + inspect.getmembers(CygApt): 270 | if isinstance(m[1], type(self.usage)) and m[1].__doc__: 271 | members.append(m); 272 | 273 | pad = max(len(m[0]) for m in members); 274 | for m in members: 275 | print(" {0} : {1}".format(m[0].ljust(pad), m[1].__doc__)); 276 | sys.stdout.write( 277 | "{LF}" 278 | " Options:{LF}" 279 | " -d, --download download only{LF}" 280 | " -h, --help show brief usage{LF}" 281 | " -m, --mirror=URL use mirror{LF}" 282 | " -t, --dist=NAME set dist name (curr, test, prev){LF}" 283 | " -x, --no-deps ignore dependencies{LF}" 284 | " -s, --regexp search as regex pattern{LF}" 285 | " -f, --nobarred add/remove packages cyg-apt depends on{LF}" 286 | " -X, --no-verify do not verify setup.ini signatures{LF}" 287 | " -y, --nopostinstall do not run postinstall scripts{LF}" 288 | " -z, --nopostremove do not run preremove/postremove scripts{LF}" 289 | " -q, --quiet loggable output - no progress indicator{LF}" 290 | "".format(LF="\n") 291 | ); 292 | 293 | def update(self, cyg_apt_rc, verify, main_mirror=None): 294 | """fetch current package database from mirror""" 295 | sig_name = None; 296 | self.__rc = cautils.parse_rc(cyg_apt_rc); 297 | 298 | if(not self.__cygwinPlatform): 299 | self.__pm = PathMapper(self.__rc.ROOT[:-1], False); 300 | 301 | if (main_mirror): 302 | mirror = main_mirror; 303 | else: 304 | mirror = self.__rc.mirror; 305 | 306 | if not mirror : 307 | raise UnexpectedValueException( 308 | "A mirror must be specified on the configuration file \"{0}\" " 309 | "or with the command line option \"--mirror\". " 310 | "See cygwin.com/mirrors.html for the list of mirrors." 311 | "".format(cyg_apt_rc) 312 | ); 313 | 314 | if not mirror[-1] == "/": 315 | sep = "/"; 316 | else: 317 | sep = ""; 318 | 319 | setup_ini_names = [ 320 | "setup.bz2", 321 | "setup.ini", 322 | ]; 323 | 324 | bag = zip(setup_ini_names, list(range(len(setup_ini_names)))); 325 | platform_dir = self.__arch+"/"; 326 | 327 | for (setup_ini_name, index) in bag: 328 | setup_ini_url = '{0}{1}{2}{3}'.format(mirror, sep, platform_dir, setup_ini_name); 329 | try: 330 | cautils.uri_get( 331 | self.__tmpDir, 332 | setup_ini_url, 333 | verbose=self.__verbose 334 | ); 335 | except ApplicationException as e: 336 | # Failed to find a possible .ini 337 | if index == len(setup_ini_names) - 1: 338 | raise e; 339 | else: 340 | continue; 341 | # Not an error to fail to find the first one 342 | # Take the first one we find 343 | break; 344 | 345 | if setup_ini_name[-4:] == ".bz2": 346 | bz_file = os.path.join(self.__tmpDir, setup_ini_name); 347 | f = open(bz_file, "rb"); 348 | compressed = f.read(); 349 | f.close(); 350 | 351 | decomp = bz2.decompress(compressed); 352 | os.remove(bz_file); 353 | setup_ini_name = "setup.ini"; 354 | 355 | f = open(os.path.join(self.__tmpDir, setup_ini_name), "wb"); 356 | f.write(decomp); 357 | f.close(); 358 | 359 | if not self.__cygwinPlatform: 360 | sys.stderr.write("WARNING can't verify setup.ini outside Cygwin.\n"); 361 | verify = False; 362 | 363 | if verify: 364 | sig_name = "{0}.sig".format(setup_ini_name); 365 | sig_url = "{0}{1}{2}{3}".format(mirror, sep, platform_dir, sig_name); 366 | try: 367 | cautils.uri_get(self.__tmpDir, sig_url, verbose=self.__verbose); 368 | except RequestException as e: 369 | msg = ( 370 | "Failed to download signature {0} Use -X to ignore " 371 | "signatures.".format(sig_url) 372 | ); 373 | raise RequestException(msg, previous=e); 374 | 375 | if self.__cygwinPlatform: 376 | gpg_path = "gpg"; 377 | else: 378 | if self._cygwinVersion() < 1.7: 379 | gpg_path = "/usr/bin/gpg"; 380 | else: 381 | gpg_path = "/usr/local/bin/gpg"; 382 | cmd = [gpg_path, "--verify", "--no-secmem-warning"]; 383 | cmd.append("{0}/{1}".format(self.__tmpDir, sig_name)); 384 | cmd.append("{0}/{1}".format(self.__tmpDir, setup_ini_name)); 385 | p = Process(cmd); 386 | p.run(); 387 | verify = p.getErrorOutput(); 388 | if isinstance(verify, bytes): 389 | marker = self.GPG_GOOD_FINGER.encode(); 390 | else: 391 | marker = self.GPG_GOOD_FINGER; 392 | if not marker in verify: 393 | msg = ( 394 | "{0} not signed by Cygwin's public key. " 395 | "Use -X to ignore signatures.".format(setup_ini_url) 396 | ); 397 | raise SignatureException(msg); 398 | 399 | downloads = os.path.join( 400 | self.__pm.mapPath(self.__rc.cache), 401 | urllib.quote(mirror+('' if mirror.endswith('/') else '/'), '').lower(), 402 | platform_dir, 403 | ); 404 | 405 | if not os.path.exists(downloads): 406 | os.makedirs(downloads); 407 | 408 | shutil.copy( 409 | os.path.join(self.__tmpDir, setup_ini_name), 410 | os.path.join(downloads, setup_ini_name) 411 | ); 412 | 413 | # BC layer for `setup_ini` configuration field 414 | if self.__rc.setup_ini : 415 | setup_ini = self.__pm.mapPath(self.__rc.setup_ini); 416 | if os.path.exists(setup_ini): 417 | shutil.copy(setup_ini, "{0}.bak".format(setup_ini)); 418 | shutil.copy( 419 | os.path.join(downloads, setup_ini_name), 420 | setup_ini 421 | ); 422 | 423 | if os.path.exists(os.path.join(self.__tmpDir, setup_ini_name)): 424 | os.remove(os.path.join(self.__tmpDir, setup_ini_name)); 425 | if sig_name: 426 | if os.path.exists(os.path.join(self.__tmpDir, sig_name)): 427 | os.remove(os.path.join(self.__tmpDir, sig_name)); 428 | 429 | def _getPre17Last(self, location): 430 | if not os.path.exists(os.path.join(location, "last-mirror")) \ 431 | or not os.path.exists(os.path.join(location, "last-cache")): 432 | return (None, None); 433 | else: 434 | f = open(os.path.join(location, "last-cache")); 435 | last_cache = f.read().strip(); 436 | f.close(); 437 | last_cache = self.__pm.mapPath(last_cache); 438 | f = open(os.path.join(location, "last-mirror")); 439 | last_mirror = f.read().strip(); 440 | f.close(); 441 | return (last_cache, last_mirror); 442 | 443 | def _writeInstalled(self, installed_db): 444 | if not self.__cygwinPlatform: 445 | raise PlatformException( 446 | "fail to create {0} only supported under Cygwin." 447 | "".format(installed_db) 448 | ); 449 | 450 | sys.stderr.write("creating {0} ... ".format(installed_db)); 451 | 452 | db_contents = CygApt.INSTALLED_DB_MAGIC; 453 | cygcheck_path = self.__pm.mapPath("/bin/cygcheck"); 454 | 455 | if os.path.exists(cygcheck_path): 456 | cmd = [cygcheck_path, "-cd"]; 457 | proc = Process(cmd); 458 | proc.mustRun(); 459 | 460 | lines = proc.getOutput().splitlines(True); 461 | # remove first two lines 462 | pkgs = lines[2:]; 463 | 464 | for pkg in pkgs: 465 | pkg = pkg.split(); 466 | db_contents += "{0} {0}-{1}.tar.bz2 0\n".format(pkg[0], pkg[1]); 467 | 468 | f = open(installed_db, 'w'); 469 | f.write(db_contents); 470 | f.close(); 471 | 472 | sys.stderr.write("OK\n"); 473 | 474 | def _gpgImport(self, uri): 475 | if not self.__cygwinPlatform: 476 | return; 477 | 478 | cautils.uri_get(self.__tmpDir, uri, verbose=self.__verbose); 479 | tmpfile = os.path.join(self.__tmpDir, os.path.basename(uri)); 480 | cmd = ["gpg"]; 481 | cmd.append("--no-secmem-warning"); 482 | cmd += ["--import", tmpfile]; 483 | Process(cmd).mustRun(); 484 | 485 | class PlatformException(ApplicationException): 486 | pass; 487 | 488 | class EnvironementException(ApplicationException): 489 | pass; 490 | 491 | class SignatureException(ApplicationException): 492 | pass; 493 | -------------------------------------------------------------------------------- /src/cygapt/structure.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | class ConfigStructure(): 15 | def __init__(self): 16 | self.ROOT = ""; 17 | self.mirror = ""; 18 | self.cache = ""; 19 | 20 | # BC layer for `setup_ini` configuration field 21 | self.setup_ini = ""; 22 | 23 | self.distname = ""; 24 | self.barred = ""; 25 | self.always_update = False; 26 | -------------------------------------------------------------------------------- /src/cygapt/test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | -------------------------------------------------------------------------------- /src/cygapt/test/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | import unittest; 18 | 19 | from cygapt.test.test_utils import TestUtils; 20 | from cygapt.test.test_url_opener import TestUrlOpener; 21 | from cygapt.test.test_argparser import TestArgParser; 22 | from cygapt.test.test_ob import TestOb; 23 | from cygapt.test.test_path_mapper import TestPathMapper; 24 | from cygapt.test.test_setup import TestSetup; 25 | from cygapt.test.test_cygapt import TestCygApt as TestCygAptClass; 26 | from cygapt.test.test_process import TestProcess; 27 | 28 | class TestCygApt(unittest.TestSuite): 29 | def __init__(self): 30 | loader = unittest.TestLoader(); 31 | self.addTests( 32 | loader.loadTestsFromTestCase(TestUtils), 33 | loader.loadTestsFromTestCase(TestUrlOpener), 34 | loader.loadTestsFromTestCase(TestArgParser), 35 | loader.loadTestsFromTestCase(TestOb), 36 | loader.loadTestsFromTestCase(TestPathMapper), 37 | loader.loadTestsFromTestCase(TestSetup), 38 | loader.loadTestsFromTestCase(TestCygAptClass), 39 | loader.loadTestsFromTestCase(TestProcess), 40 | ); 41 | 42 | if __name__ == "__main__": 43 | unittest.main(); 44 | -------------------------------------------------------------------------------- /src/cygapt/test/case/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import sys; 17 | import warnings; 18 | import pprint; 19 | import functools; 20 | import re; 21 | 22 | if sys.version_info < (3, ): 23 | from .py2 import TestCase as BaseTestCase; 24 | else: 25 | from unittest import TestCase as BaseTestCase; 26 | 27 | __unittest = True; 28 | 29 | class DataProviderTestCase(BaseTestCase): 30 | """Provide the PHPUnit dataProvider feature. 31 | 32 | Use it with the `dataProvider` decorator. 33 | 34 | @author: alquerci 35 | """ 36 | ATTR_METHOD_NAME = 'dataProviderMethodName'; 37 | 38 | def __init__(self, methodName='runTest', data=None, dataId=None): 39 | BaseTestCase.__init__(self, methodName); 40 | 41 | self.__data = data; 42 | self.__dataId = dataId; 43 | 44 | # wrapped the test method to allow to be called with data as arguments 45 | if None is not self.__data: 46 | self.__testMethod = getattr(self, methodName); 47 | 48 | functools.update_wrapper( 49 | self.__invokeWithData.__func__, 50 | self.__testMethod.__func__, 51 | ); 52 | setattr(self, self._testMethodName, self.__invokeWithData); 53 | 54 | def __str__(self): 55 | string = BaseTestCase.__str__(self); 56 | 57 | data = self.__data; 58 | if data: 59 | # cuts long data representation 60 | dataRepr = pprint.saferepr(data); 61 | if 80 < len(dataRepr) : 62 | dataRepr = '{0}...{1}'.format(dataRepr[:70], dataRepr[-7:]); 63 | 64 | string += ' with data set #{0:d} {1}'.format( 65 | self.__dataId, 66 | dataRepr, 67 | ); 68 | 69 | return string; 70 | 71 | def run(self, result=None): 72 | testMethod = getattr(self, self._testMethodName); 73 | 74 | # gets the data provider method name 75 | try: 76 | providerName = getattr(testMethod, self.ATTR_METHOD_NAME); 77 | except AttributeError: 78 | return BaseTestCase.run(self, result); 79 | 80 | # call the data provider method for gets all datasets 81 | datas = getattr(self, providerName)(); 82 | 83 | # runs the test with all datasets 84 | for dataId in range(len(datas)): 85 | # use the same behavior than the `TestSuite.run` method 86 | if result and result.shouldStop: 87 | break; 88 | 89 | # create a clone of the current instance with this data set 90 | that = type(self)(self._testMethodName, datas[dataId], dataId); 91 | 92 | BaseTestCase.run(that, result); 93 | 94 | def __invokeWithData(self): 95 | return self.__testMethod(*self.__data); 96 | 97 | 98 | class TestCase(DataProviderTestCase): 99 | """Base class for all cygapt test case. 100 | """ 101 | def _assertDeprecatedWarning(self, message, callback, *args, **kwargs): 102 | """Asserts that a warning of type "DeprecationWarning" is triggered with message. 103 | 104 | When invoked the callback with arguments args and keyword arguments 105 | kwargs. 106 | 107 | @param message: str The expected warning message 108 | @param callback: callable The callback to call 109 | @param *args: mixed 110 | @param **kwargs: mixed 111 | """ 112 | assert isinstance(message, str); 113 | assert hasattr(callback, '__call__'); 114 | 115 | with warnings.catch_warnings(record=True) as warnList : 116 | # cause all DeprecationWarning to always be triggered 117 | warnings.simplefilter("always", DeprecationWarning); 118 | 119 | # trigger a warning 120 | ret = callback(*args, **kwargs); 121 | 122 | # verify some things 123 | if not warnList : 124 | self.fail(" ".join([ 125 | "Failed asserting that a warning of type", 126 | '"DeprecationWarning" is triggered', 127 | ])); 128 | 129 | messages = list(); 130 | for warn in warnList : 131 | messages.append(str(warn.message)); 132 | if message in messages[-1] : 133 | return ret; 134 | 135 | self.fail("\n".join([ 136 | "Failed asserting that at least one of these warning messages:", 137 | "{0}", 138 | "contains", 139 | "{1}", 140 | ]).format("\n".join(messages), message)); 141 | 142 | return ret; 143 | 144 | def _assertNotDeprecatedWarning(self, message, callback, *args, **kwargs): 145 | """Asserts that a warning of type "DeprecationWarning" is not triggered with message. 146 | 147 | When invoked the callback with arguments args and keyword arguments 148 | kwargs. 149 | 150 | @param message: str The expected warning message 151 | @param callback: callable The callback to call 152 | @param *args: mixed 153 | @param **kwargs: mixed 154 | """ 155 | try: 156 | self._assertDeprecatedWarning(message, callback, *args, **kwargs); 157 | except self.failureException : 158 | return; 159 | 160 | self.fail(" ".join([ 161 | "Failed asserting that a warning of type", 162 | '"DeprecationWarning" with message "{0}" is not triggered', 163 | ]).format(message)); 164 | 165 | 166 | def dataProvider(methodName): 167 | """Method decorator like the PHPUnit one. 168 | 169 | The method must return a sequence of sequences that they will be pass 170 | as arguments of the decorated method. 171 | 172 | It does not support keyword argument. 173 | 174 | @param methodName: str The method name of the current class to call for get 175 | all datas 176 | """ 177 | def decorator(function): 178 | setattr(function, DataProviderTestCase.ATTR_METHOD_NAME, methodName); 179 | 180 | return function; 181 | 182 | return decorator; 183 | 184 | 185 | def expectedException(expectedClass, expectedMessage=None, treatMessageAsRegexp=False): 186 | """TestCase method decorator. 187 | 188 | @param expectedClass: type The expected exception class 189 | @param expectedMessage: str The expected message that the raises exception contain 190 | @param treatMessageAsRegexp: bool Treat the expected message as a regex pattern 191 | """ 192 | if None is not expectedMessage and not treatMessageAsRegexp: 193 | expectedMessage = re.escape(expectedMessage); 194 | 195 | def decorator(function): 196 | @functools.wraps(function) 197 | def wrapper(self, *args, **kwargs): 198 | with self.assertRaisesRegexp(expectedClass, expectedMessage): 199 | return function(self, *args, **kwargs); 200 | 201 | return wrapper; 202 | 203 | return decorator; 204 | -------------------------------------------------------------------------------- /src/cygapt/test/case/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import sys; 17 | 18 | if sys.version_info < (3, ): 19 | from .py2.exception import SkipTestException as SkipTestException; 20 | else: 21 | from unittest import SkipTest as SkipTestException; 22 | -------------------------------------------------------------------------------- /src/cygapt/test/case/py2/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import sys; 17 | 18 | if sys.version_info < (2, 7): 19 | from .minor6 import TestCase; 20 | else: 21 | from unittest import TestCase; 22 | -------------------------------------------------------------------------------- /src/cygapt/test/case/py2/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import sys; 17 | 18 | if sys.version_info < (2, 7): 19 | from .minor6.exception import SkipTestException; 20 | else: 21 | from unittest import SkipTest as SkipTestException; 22 | -------------------------------------------------------------------------------- /src/cygapt/test/case/py2/minor6/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | from unittest import TestCase as BaseTestCase; 17 | from unittest import _TextTestResult; 18 | from unittest import TestResult; 19 | import re; 20 | 21 | from .exception import SkipTestException; 22 | 23 | __unittest = True; 24 | 25 | class TestCase(BaseTestCase): 26 | """Backports some useful feature from newer unittest version. 27 | """ 28 | 29 | def run(self, result=None): 30 | """Runs the test. 31 | 32 | @param result: TestResult 33 | """ 34 | assert None is result or isinstance(result, TestResult); 35 | 36 | if None is result : 37 | result = self.defaultTestResult(); 38 | 39 | if not isinstance(result, _TestResultWrapper) : 40 | result = _TestResultWrapper(result); 41 | 42 | return BaseTestCase.run(self, result); 43 | 44 | def skipTest(self, reason): 45 | """Skip this test. 46 | 47 | @param reason: str Explain why this test is skip 48 | 49 | @raise SkipTest: Always 50 | """ 51 | assert isinstance(reason, str); 52 | 53 | raise SkipTestException(reason); 54 | 55 | def assertRaises(self, excClass, callableObj=None, *args, **kwargs): 56 | """Fail unless an exception of class excClass is raised 57 | by callableObj when invoked with arguments args and keyword 58 | arguments kwargs. If a different type of exception is 59 | raised, it will not be caught, and the test case will be 60 | deemed to have suffered an error, exactly as for an 61 | unexpected exception. 62 | 63 | If called with callableObj omitted or None, will return a 64 | context object used like this:: 65 | 66 | with self.assertRaises(SomeException): 67 | do_something() 68 | 69 | The context manager keeps a reference to the exception as 70 | the 'exception' attribute. This allows you to inspect the 71 | exception after the assertion:: 72 | 73 | with self.assertRaises(SomeException) as cm: 74 | do_something() 75 | the_exception = cm.exception 76 | self.assertEqual(the_exception.error_code, 3) 77 | """ 78 | context = _AssertRaisesContext(excClass, self) 79 | if callableObj is None: 80 | return context 81 | with context: 82 | callableObj(*args, **kwargs) 83 | 84 | def assertRaisesRegexp(self, expected_exception, expected_regexp, 85 | callable_obj=None, *args, **kwargs): 86 | """Asserts that the message in a raised exception matches a regexp. 87 | 88 | Args: 89 | expected_exception: Exception class expected to be raised. 90 | expected_regexp: Regexp (re pattern object or string) expected 91 | to be found in error message. 92 | callable_obj: Function to be called. 93 | args: Extra args. 94 | kwargs: Extra kwargs. 95 | """ 96 | if expected_regexp is not None: 97 | expected_regexp = re.compile(expected_regexp) 98 | context = _AssertRaisesContext(expected_exception, self, expected_regexp) 99 | if callable_obj is None: 100 | return context 101 | with context: 102 | callable_obj(*args, **kwargs) 103 | 104 | 105 | class _AbstractTestResultWrapper(TestResult): 106 | """Backports some useful feature from newer unittest version. 107 | """ 108 | 109 | def __init__(self, result): 110 | """Constructor. 111 | 112 | @param result: TestResult The wrapped object 113 | """ 114 | assert isinstance(result, TestResult); 115 | 116 | TestResult.__init__(self); 117 | 118 | self._result = result; 119 | 120 | def startTest(self, test): 121 | """Called when the given test is about to be run. 122 | 123 | @param test: TestCase The case where the skip come from 124 | """ 125 | assert isinstance(test, BaseTestCase); 126 | 127 | return self._result.startTest(test); 128 | 129 | def stopTest(self, test): 130 | """Called when the given test has been run. 131 | 132 | @param test: TestCase The case where the skip come from 133 | """ 134 | assert isinstance(test, BaseTestCase); 135 | 136 | return self._result.stopTest(test); 137 | 138 | def addError(self, test, err): 139 | """Called when an error has occurred. 140 | 141 | @param test: TestCase The case where the error come from 142 | @param err: tuple Returned by sys.exc_info() 143 | """ 144 | assert isinstance(test, BaseTestCase); 145 | assert isinstance(err, tuple); 146 | 147 | return self._result.addError(test, err); 148 | 149 | def addFailure(self, test, err): 150 | """Called when a test failed. 151 | 152 | @param test: TestCase The case where the failure come from 153 | @param err: tuple Returned by sys.exc_info() 154 | """ 155 | assert isinstance(test, BaseTestCase); 156 | assert isinstance(err, tuple); 157 | 158 | return self._result.addFailure(test, err); 159 | 160 | def addSuccess(self, test): 161 | """Called when a test has completed successfully. 162 | 163 | @param test: TestCase The case where the success come from 164 | """ 165 | assert isinstance(test, BaseTestCase); 166 | 167 | return self._result.addSuccess(test); 168 | 169 | def wasSuccessful(self): 170 | """Tells whether or not this result was a success. 171 | 172 | @return: bool 173 | """ 174 | return self._result.wasSuccessful(); 175 | 176 | def stop(self): 177 | """Indicates that the tests should be aborted. 178 | """ 179 | return self._result.stop(); 180 | 181 | class _TestResultWrapper(_AbstractTestResultWrapper): 182 | """Backports some useful feature from newer unittest version. 183 | """ 184 | 185 | def addError(self, test, err): 186 | """Called when an error has occurred. 187 | 188 | @param test: TestCase The case where the error come from 189 | @param err: tuple Returned by sys.exc_info() 190 | """ 191 | assert isinstance(test, BaseTestCase); 192 | assert isinstance(err, tuple); 193 | 194 | exception = err[1]; 195 | if isinstance(exception, SkipTestException) : 196 | return self._addSkip(test, str(exception)); 197 | 198 | return _AbstractTestResultWrapper.addError(self, test, err); 199 | 200 | def _addSkip(self, test, reason): 201 | """Called when a test is skipped. 202 | 203 | @param test: TestCase The case where the skip come from 204 | @param reason: str Explain why a test has been skip 205 | """ 206 | assert isinstance(test, BaseTestCase); 207 | assert isinstance(reason, str); 208 | 209 | if not isinstance(self._result, _TextTestResult) : 210 | return; 211 | 212 | if self._result.showAll : 213 | self._result.stream.writeln("skipped {0!r}".format(reason)); 214 | elif self._result.dots : 215 | self._result.stream.write("s"); 216 | self._result.stream.flush(); 217 | 218 | 219 | class _AssertRaisesContext(object): 220 | """A context manager used to implement TestCase.assertRaises* methods.""" 221 | 222 | def __init__(self, expected, test_case, expected_regexp=None): 223 | self.expected = expected 224 | self.failureException = test_case.failureException 225 | self.expected_regexp = expected_regexp 226 | 227 | def __enter__(self): 228 | return self 229 | 230 | def __exit__(self, exc_type, exc_value, tb): 231 | if exc_type is None: 232 | try: 233 | exc_name = self.expected.__name__ 234 | except AttributeError: 235 | exc_name = str(self.expected) 236 | raise self.failureException( 237 | "{0} not raised".format(exc_name)) 238 | if not issubclass(exc_type, self.expected): 239 | # let unexpected exceptions pass through 240 | return False 241 | self.exception = exc_value # store for later retrieval 242 | if self.expected_regexp is None: 243 | return True 244 | 245 | expected_regexp = self.expected_regexp 246 | if not expected_regexp.search(str(exc_value)): 247 | raise self.failureException('"%s" does not match "%s"' % 248 | (expected_regexp.pattern, str(exc_value))) 249 | return True 250 | -------------------------------------------------------------------------------- /src/cygapt/test/case/py2/minor6/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | class SkipTestException(Exception): 17 | """Raise this exception in a test to skip it. 18 | """ 19 | -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/README: -------------------------------------------------------------------------------- 1 | Files in this directory: 2 | 3 | cyglsa.dll : from 32-bit Cygwin 4 | cyglsa64.dll : from 64-bit Cygwin 5 | 6 | cyglsa-bad*.dll : (copies of cyglsa.dll with changes) 7 | 8 | cyglsa-bad1.dll : MZ header changed to ZM 9 | cyglsa-bad2.dll : PE\0\0 header changed to EP\0\0 10 | cyglsa-bad3.dll : machine value changed from 0x014C to 0xDEAD 11 | cyglsa-bad4.dll : truncated before PE header 12 | -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa-bad1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa-bad1.dll -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa-bad2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa-bad2.dll -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa-bad3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa-bad3.dll -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa-bad4.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa-bad4.dll -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa.dll -------------------------------------------------------------------------------- /src/cygapt/test/fixtures/utils/cyglsa64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylen/cyg-apt/7b6848d18eb210f85c86990f5aa3c97a66964eb7/src/cygapt/test/fixtures/utils/cyglsa64.dll -------------------------------------------------------------------------------- /src/cygapt/test/test_argparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | """ 15 | Unit test for cygapt.argparser 16 | """ 17 | 18 | from __future__ import absolute_import; 19 | 20 | import unittest; 21 | import sys; 22 | 23 | from cygapt.test.case import TestCase; 24 | from cygapt.test.case import dataProvider; 25 | from cygapt.argparser import CygAptArgParser; 26 | 27 | class TestArgParser(TestCase): 28 | def setUp(self): 29 | TestCase.setUp(self); 30 | self.obj = CygAptArgParser("scriptname"); 31 | 32 | self.__originArgv = sys.argv[:]; 33 | sys.argv = sys.argv[:1]; 34 | 35 | def tearDown(self): 36 | sys.argv = self.__originArgv; 37 | 38 | TestCase.tearDown(self); 39 | 40 | def test___init__(self): 41 | self.assertTrue(isinstance(self.obj, CygAptArgParser)); 42 | self.assertEqual(self.obj.getAppName(), "scriptname"); 43 | 44 | def testParse(self): 45 | sys.argv.append("install"); 46 | sys.argv.append("pkg"); 47 | 48 | ret = self.obj.parse(); 49 | 50 | self.assertTrue(ret.verbose); 51 | self.assertEqual(ret.command, "install"); 52 | self.assertEqual(ret.package, ['pkg']); 53 | 54 | def testArgumentType(self): 55 | sys.argv.append("--mirror=http://a.mirror.str"); 56 | sys.argv.append("update"); 57 | sys.argv.append("--dist=test"); 58 | 59 | ret = self.obj.parse(); 60 | 61 | self.assertEqual("test", ret.distname); 62 | self.assertEqual("http://a.mirror.str", ret.mirror); 63 | 64 | 65 | def testArgumentTypeDefault(self): 66 | ret = self.obj.parse(); 67 | 68 | self.assertEqual("curr", ret.distname); 69 | self.assertEqual(True, ret.verbose); 70 | 71 | def testNoPostInstallOptionIsDeprecated(self): 72 | sys.argv.append("-y"); 73 | 74 | ret = self._assertDeprecatedWarning( 75 | "The option -y, --nopostinstall is deprecated since version " 76 | "1.1 and will be removed in 2.0.", 77 | self.obj.parse 78 | ); 79 | 80 | self.assertTrue(ret.nopostinstall); 81 | 82 | def testNoPostRemoveOptionIsDeprecated(self): 83 | sys.argv.append("-z"); 84 | 85 | ret = self._assertDeprecatedWarning( 86 | "The option -z, --nopostremove is deprecated since version " 87 | "1.1 and will be removed in 2.0.", 88 | self.obj.parse 89 | ); 90 | 91 | self.assertTrue(ret.nopostremove); 92 | 93 | def testMd5CommandIsDeprecated(self): 94 | sys.argv.append("md5"); 95 | 96 | ret = self._assertDeprecatedWarning( 97 | "The command md5 is deprecated since version 1.2 and will be " 98 | "removed in 2.0, use checksum instead.", 99 | self.obj.parse 100 | ); 101 | 102 | self.assertEqual(ret.command, "checksum"); 103 | 104 | @dataProvider('getParseCommandData') 105 | def testParseCommand(self, command, args=None): 106 | """ 107 | @param command: str 108 | @param args: list 109 | """ 110 | if None is args : 111 | args = list(); 112 | 113 | sys.argv.append(command); 114 | for arg in args : 115 | sys.argv.append(arg); 116 | 117 | ret = self.obj.parse(); 118 | 119 | self.assertEqual(ret.command, command); 120 | self.assertEqual(ret.package, args); 121 | 122 | def getParseCommandData(self): 123 | return [ 124 | ['postinstall'], 125 | ['postremove', ['pkg']], 126 | ['checksum', ['pkg']], 127 | ]; 128 | 129 | if __name__ == "__main__": 130 | unittest.main(); 131 | -------------------------------------------------------------------------------- /src/cygapt/test/test_ob.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | """ 15 | unit test for cygapt.ob 16 | """ 17 | 18 | from __future__ import print_function; 19 | from __future__ import absolute_import; 20 | 21 | import unittest; 22 | import sys; 23 | 24 | from cygapt.test.case import TestCase; 25 | from cygapt.ob import CygAptOb; 26 | 27 | REPR_STDOUT = repr(sys.stdout); 28 | 29 | class TestOb(TestCase): 30 | def setUp(self): 31 | TestCase.setUp(self); 32 | self.obj = CygAptOb(False); 33 | 34 | def tearDown(self): 35 | self.obj._end(); 36 | TestCase.tearDown(self); 37 | 38 | def makeOn(self): 39 | txt = "TestOb.makeOn "; 40 | print(txt, end=""); 41 | value = sys.stdout.getvalue(); 42 | self.assertTrue(value.endswith(txt)); 43 | self.assertTrue(self.obj._state); 44 | 45 | def makeOff(self): 46 | self.assertEqual(repr(sys.stdout), REPR_STDOUT); 47 | self.assertFalse(self.obj._state); 48 | 49 | def test__init__(self): 50 | self.assertTrue(isinstance(self.obj, CygAptOb)); 51 | 52 | def testClean(self): 53 | self.obj.clean(); 54 | self.makeOff(); 55 | self.obj.start(); 56 | self.makeOn(); 57 | self.obj.clean(); 58 | self.assertEqual(self.obj._buffer.getvalue(), ""); 59 | self.assertNotEqual(self.obj._value, ""); 60 | self.assertNotEqual(self.obj.getContents(), ""); 61 | 62 | def testStartEnd(self): 63 | self.obj._end(); 64 | self.makeOff(); 65 | self.obj.start(); 66 | self.makeOn(); 67 | self.obj._end(); 68 | self.makeOff(); 69 | 70 | def testEndClean(self): 71 | self.obj.endClean(); 72 | self.makeOff(); 73 | self.obj.start(); 74 | self.makeOn(); 75 | self.obj.endClean(); 76 | self.makeOff(); 77 | 78 | def testEndFlush(self): 79 | self.obj.endFlush(); 80 | self.makeOff(); 81 | self.obj.start(); 82 | self.makeOn(); 83 | self.obj.endFlush(); 84 | self.makeOff(); 85 | 86 | def testFlush(self): 87 | self.obj.flush(); 88 | self.makeOff(); 89 | self.obj.start(); 90 | self.makeOn(); 91 | self.obj.flush(); 92 | self.makeOn(); 93 | 94 | def testGetClean(self): 95 | ret = self.obj.getClean(); 96 | self.assertFalse(ret); 97 | self.makeOff(); 98 | self.obj.start(); 99 | self.makeOn(); 100 | t = self.obj._buffer.getvalue(); 101 | txt = "TestOb.test_getClean"; 102 | print(txt); 103 | ret = self.obj.getClean(); 104 | self.assertEqual(ret, "{0}{1}\n".format(t, txt)); 105 | self.makeOff(); 106 | 107 | def testGetContent(self): 108 | ret = self.obj.getContents(); 109 | self.assertFalse(ret); 110 | self.makeOff(); 111 | self.obj.start(); 112 | txt = "TestOb.test_get_content"; 113 | print(txt); 114 | ret = self.obj.getContents(); 115 | self.assertEqual(ret, "{0}\n".format(txt)); 116 | self.makeOn(); 117 | 118 | def testGetFlush(self): 119 | ret = self.obj.getFlush(); 120 | self.assertFalse(ret); 121 | self.makeOff(); 122 | self.obj.start(); 123 | txt = "TestOb.test_getFlush"; 124 | print(txt); 125 | ret = self.obj.getFlush(); 126 | self.assertEqual(ret, "{0}\n".format(txt)); 127 | self.makeOff(); 128 | 129 | def testGetLength(self): 130 | ret = self.obj.getLength(); 131 | self.assertFalse(ret); 132 | self.makeOff(); 133 | self.obj.start(); 134 | length = 10; 135 | print("t" * length); 136 | ret = self.obj.getLength(); 137 | self.assertEqual(ret, length + 1); 138 | self.makeOn(); 139 | 140 | def testImplicitFlush(self): 141 | self.obj.implicitFlush(True); 142 | self.makeOff(); 143 | self.obj.implicitFlush(False); 144 | self.makeOn(); 145 | 146 | if __name__ == "__main__": 147 | unittest.main(); 148 | -------------------------------------------------------------------------------- /src/cygapt/test/test_path_mapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | import unittest; 18 | import sys; 19 | from tempfile import TemporaryFile; 20 | 21 | from cygapt.test.case import TestCase; 22 | from cygapt.test.case import dataProvider; 23 | from cygapt.path_mapper import PathMapper; 24 | 25 | class TestPathMapper(TestCase): 26 | def setUp(self): 27 | TestCase.setUp(self); 28 | self._var_root = "C:/cygwin"; 29 | self._var_cygwin_p = sys.platform.startswith("cygwin"); 30 | 31 | def test__init__(self): 32 | pm = self._createPathMapper(self._var_root, self._var_cygwin_p); 33 | 34 | self.assertTrue(isinstance(pm, PathMapper)); 35 | self.assertEqual(self._var_root, pm.getRoot()); 36 | 37 | def testAddMapping(self): 38 | pm = self._createPathMapper(self._var_root, self._var_cygwin_p); 39 | 40 | mount = ( 41 | "C:/cygwin/bin on /usr/bin type ntfs (binary,auto){LF}" 42 | "C:/cygwin/lib on /usr/lib type ntfs (binary,auto){LF}" 43 | "C:/cygwin on / type ntfs (binary,auto){LF}" 44 | "C: on /cygdrive/c type ntfs (binary,posix=0,user,noumount,auto){LF}" 45 | "".format(LF="\n") 46 | ); 47 | f = TemporaryFile(mode='w+'); 48 | f.writelines(mount); 49 | f.seek(0); 50 | mtab = f.readlines(); 51 | f.close(); 52 | 53 | mapping = { 54 | '/usr/bin/': "C:/cygwin/bin/", 55 | '/usr/lib/': "C:/cygwin/lib/", 56 | '/cygdrive/c/': "C:/", 57 | }; 58 | pm._addMapping(mtab); 59 | self.assertEqual(pm.getMap(), mapping); 60 | self.assertEqual(pm.getMountRoot(), "C:/cygwin/"); 61 | 62 | def testMapPath(self): 63 | pm = self._createPathMapper(self._var_root, True); 64 | 65 | self.assertEqual(pm.mapPath("/usr/bin/"), "/usr/bin/"); 66 | 67 | @dataProvider('getMapPathOutsideCygwinData') 68 | def testMapPathOutsideCygwin(self, path, expected, message=None): 69 | pm = self._createPathMapper(self._var_root, False); 70 | 71 | pm.setMap({ 72 | '/usr/bin/': 'C:/cygwin/bin/', 73 | '/usr/lib/': 'C:/cygwin/lib/', 74 | '/cygdrive/c/': 'C:/', 75 | }); 76 | 77 | self.assertEqual(pm.mapPath(path), expected, message); 78 | 79 | def getMapPathOutsideCygwinData(self): 80 | return [ 81 | ['/usr/bin/', 'C:/cygwin/bin/'], 82 | ['C:/cygwin/lib/', 'C:/cygwin/lib/', 'Does not map path that has been already mapped'], 83 | ['/cygdrive/c', 'C:', 'Work without ending slash'], 84 | ['/cygdrive/c/foo/cygdrive/c/', 'C:/foo/cygdrive/c/', 'Replaced only at the beginning of the path'], 85 | ['/', 'C:/cygwin/'], 86 | ]; 87 | 88 | def _createPathMapper(self, root='', cygwin_p=False): 89 | return PathMapper(root, cygwin_p); 90 | 91 | if __name__ == "__main__": 92 | unittest.main(); 93 | -------------------------------------------------------------------------------- /src/cygapt/test/test_process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | """ 15 | Unit test for cygapt.process 16 | """ 17 | 18 | from __future__ import absolute_import; 19 | 20 | import os; 21 | import unittest; 22 | import subprocess; 23 | from tempfile import TemporaryFile; 24 | 25 | from cygapt.test.case import TestCase; 26 | from cygapt.test.case import dataProvider; 27 | from cygapt.test.case import expectedException; 28 | from cygapt.process import Process; 29 | from cygapt.process.exception import ProcessFailedException; 30 | from cygapt.process.exception import LogicException; 31 | 32 | class TestProcess(TestCase): 33 | def setUp(self): 34 | TestCase.setUp(self); 35 | 36 | def tearDown(self): 37 | TestCase.tearDown(self); 38 | 39 | def getDefaultGetterSetterData(self): 40 | return [ 41 | ['CommandLine', ['foo']], 42 | ['WorkingDirectory', ['foo']], 43 | ['CommandLine', 'bar'], 44 | ['WorkingDirectory', 'bar'], 45 | ['Input', 'bar'], 46 | ]; 47 | 48 | @dataProvider('getDefaultGetterSetterData') 49 | def testDefaultGetterSetter(self, fn, value): 50 | proc = Process('python'); 51 | 52 | setter = 'set'+fn; 53 | getter = 'get'+fn; 54 | 55 | self.assertTrue( 56 | None is getattr(proc, setter)(value), 57 | "Failed asserting that setter method return None", 58 | ); 59 | 60 | self.assertEqual(getattr(proc, getter)(), value); 61 | 62 | if not isinstance(value, str) : 63 | self.assertFalse( 64 | value is getattr(proc, getter)(), 65 | "Failed asserting that getter method return a cloned value", 66 | ); 67 | 68 | def responsesCodeProvider(self): 69 | return [ 70 | [1, 'getExitCode', 'exit(1);'], 71 | [0, 'getExitCode', 'exit();'], 72 | ['Misuse of shell builtins', 'getExitCodeText', 'exit(2);'], 73 | ['Unknown error', 'getExitCodeText', 'exit(64);'], 74 | ['foo', 'getOutput', 'import sys; sys.stdout.write("foo");'], 75 | ['bar', 'getOutput', 'import sys; sys.stdout.write("bar");'], 76 | ['0', 'getOutput', 'import sys; sys.stdout.write("0");'], 77 | ['foo', 'getErrorOutput', 'import sys; sys.stderr.write("foo");'], 78 | ['bar', 'getErrorOutput', 'import sys; sys.stderr.write("bar");'], 79 | ]; 80 | 81 | @dataProvider('responsesCodeProvider') 82 | def testProcessResponses(self, expected, getter, code): 83 | proc = Process("python -c '{0}'".format(code)); 84 | proc.run(); 85 | 86 | self.assertEqual(getattr(proc, getter)(), expected); 87 | 88 | @dataProvider('responsesCodeProvider') 89 | def testRunReturnAlwaysExitCode(self, expected, getter, code): 90 | proc = Process("python -c '{0}'".format(code)); 91 | actual = proc.run(); 92 | 93 | self.assertEqual(actual, proc.getExitCode()); 94 | 95 | def testRunReturnAlwaysExitCodeEvenOnCommandFailed(self): 96 | proc = Process('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); 97 | actual = proc.run(); 98 | 99 | self.assertTrue(0 < actual, "Failed asserting that {0} is greater than 0".format(actual)); 100 | 101 | def getProcessPipesData(self): 102 | return [ 103 | [1, 'getOutput', 'import sys; sys.stdout.write(sys.stdin.read());'], 104 | [1, 'getErrorOutput', 'import sys; sys.stderr.write(sys.stdin.read());'], 105 | [16, 'getOutput', 'import sys; sys.stdout.write(sys.stdin.read());'], 106 | [16, 'getErrorOutput', 'import sys; sys.stderr.write(sys.stdin.read());'], 107 | [64, 'getOutput', 'import sys; sys.stdout.write(sys.stdin.read());'], 108 | [64, 'getErrorOutput', 'import sys; sys.stderr.write(sys.stdin.read());'], 109 | [1024, 'getOutput', 'import sys; sys.stdout.write(sys.stdin.read());'], 110 | [1024, 'getErrorOutput', 'import sys; sys.stderr.write(sys.stdin.read());'], 111 | [4096, 'getOutput', 'import sys; sys.stdout.write(sys.stdin.read());'], 112 | [4096, 'getErrorOutput', 'import sys; sys.stderr.write(sys.stdin.read());'], 113 | ]; 114 | 115 | @dataProvider('getProcessPipesData') 116 | def testProcessPipes(self, size, getter, code): 117 | expected = '*' * 1024 * size + '!'; 118 | expectedLength = 1024 * size + 1; 119 | 120 | proc = Process("python -c '{0}'".format(code)); 121 | proc.setInput(expected); 122 | proc.run(); 123 | 124 | self.assertEqual(len(getattr(proc, getter)()), expectedLength); 125 | self.assertEqual(proc.getExitCode(), 0); 126 | 127 | @dataProvider('getProcessPipesData') 128 | def testSetStreamAsInput(self, size, getter, code): 129 | expected = '*' * 1024 * size + '!'; 130 | expectedLength = 1024 * size + 1; 131 | 132 | stream = TemporaryFile(); 133 | stream.write(expected); 134 | stream.seek(0); 135 | 136 | proc = Process("python -c '{0}'".format(code)); 137 | proc.setInput(stream); 138 | proc.run(); 139 | 140 | stream.close(); 141 | 142 | self.assertEqual(len(getattr(proc, getter)()), expectedLength); 143 | self.assertEqual(proc.getExitCode(), 0); 144 | 145 | @dataProvider('provideInvalidInputValues') 146 | @expectedException( 147 | TypeError, 148 | 'Process.setInput only accepts strings or file-like objects.' 149 | ) 150 | def testInvalidInput(self, value): 151 | process = Process('python --version'); 152 | process.setInput(value); 153 | 154 | def provideInvalidInputValues(self): 155 | return [ 156 | [list()], 157 | [object()], 158 | ]; 159 | 160 | @dataProvider('provideInputValues') 161 | def testValidInput(self, expected, value): 162 | process = Process('python --version'); 163 | process.setInput(value); 164 | 165 | self.assertEqual(process.getInput(), expected); 166 | 167 | def provideInputValues(self): 168 | return [ 169 | [None, None], 170 | ['24.5', 24.5], 171 | ['input data', 'input data'], 172 | ]; 173 | 174 | def testExitCodeCommandFailed(self): 175 | process = Process('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); 176 | process.run(); 177 | 178 | actual = process.getExitCode(); 179 | self.assertTrue(0 < actual, "Failed asserting that {0} is greater than 0".format(actual)); 180 | 181 | def testExitCodeTextIsNoneWhenExitCodeIsNone(self): 182 | process = Process(''); 183 | 184 | self.assertEqual(process.getExitCodeText(), None); 185 | 186 | def getResponsesCommandFailedData(self): 187 | return [ 188 | ['', 'getOutput'], 189 | ['', 'getErrorOutput'], 190 | ]; 191 | 192 | @dataProvider('getResponsesCommandFailedData') 193 | def testResponsesCommandFailed(self, expected, method): 194 | process = Process('python -c \'import sys; sys.stdout.write("foo"); sys.stderr.write("bar");\''); 195 | process.run(); 196 | process.setCommandLine('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); 197 | process.run(); 198 | 199 | self.assertEqual(getattr(process, method)(), expected); 200 | 201 | def testMustRun(self): 202 | process = Process('python -c \'import sys; sys.stdout.write("foo");\''); 203 | process.mustRun(); 204 | 205 | self.assertEqual(process.getOutput(), 'foo'); 206 | self.assertEqual(process.getExitCode(), 0); 207 | 208 | @expectedException(ProcessFailedException, """\ 209 | The command "python -c 'import sys; sys.stdout.write("foo"); sys.stderr.write("bar"); exit(1);'" failed. 210 | Exit Code: 1 (General error) 211 | 212 | Output: 213 | ================ 214 | foo 215 | 216 | Error Output: 217 | ================ 218 | bar\ 219 | """) 220 | def testMustRunThrowsException(self): 221 | process = Process('python -c \'import sys; sys.stdout.write("foo"); sys.stderr.write("bar"); exit(1);\''); 222 | process.mustRun(); 223 | 224 | @expectedException(LogicException, 'Process must be started before calling getOutput') 225 | def testGetOutputProcessNotStarted(self): 226 | proc = Process('python --version'); 227 | proc.getOutput(); 228 | 229 | @expectedException(LogicException, 'Process must be started before calling getErrorOutput') 230 | def testGetErrorOutputProcessNotStarted(self): 231 | proc = Process('python --version'); 232 | proc.getErrorOutput(); 233 | 234 | def testConstructor(self): 235 | proc = Process('foo'); 236 | 237 | self.assertEqual(proc.getCommandLine(), 'foo'); 238 | self.assertEqual(proc.getWorkingDirectory(), None); 239 | self.assertEqual(proc.getInput(), None); 240 | 241 | def testConstructorSetTheCwd(self): 242 | proc = Process('foo', 'bar'); 243 | 244 | self.assertEqual(proc.getWorkingDirectory(), 'bar'); 245 | 246 | def provideStartMethods(self): 247 | return [ 248 | ['run'], 249 | ['mustRun'], 250 | ]; 251 | 252 | @dataProvider('provideStartMethods') 253 | def testRunWorkingDirectoryIsUsed(self, startMethod): 254 | popen = subprocess.Popen; 255 | directory = os.path.dirname(__file__); 256 | 257 | def mock(*args, **kargs): 258 | self.assertEqual(kargs['cwd'], directory); 259 | 260 | return popen(*args, **kargs); 261 | 262 | subprocess.Popen = mock; 263 | 264 | try: 265 | proc = Process('python --version'); 266 | proc.setWorkingDirectory(directory); 267 | 268 | getattr(proc, startMethod)(); 269 | finally: 270 | subprocess.Popen = popen; 271 | 272 | @dataProvider('provideStartMethods') 273 | def testRunInheritOutput(self, startMethod): 274 | popen = subprocess.Popen; 275 | 276 | def mock(*args, **kargs): 277 | self.assertTrue(None is kargs['stdout']); 278 | self.assertTrue(None is kargs['stderr']); 279 | 280 | return popen(*args, **kargs); 281 | 282 | subprocess.Popen = mock; 283 | 284 | try: 285 | proc = Process("python -c ''"); 286 | 287 | getattr(proc, startMethod)(True); 288 | finally: 289 | subprocess.Popen = popen; 290 | 291 | if __name__ == "__main__": 292 | unittest.main(); 293 | -------------------------------------------------------------------------------- /src/cygapt/test/test_setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | """ 15 | Unit test for cygapt.setup 16 | """ 17 | 18 | from __future__ import absolute_import; 19 | 20 | import unittest; 21 | import sys; 22 | import os; 23 | import urllib; 24 | import re; 25 | 26 | from cygapt.setup import CygAptSetup; 27 | from cygapt.test.case import dataProvider; 28 | from cygapt.test.utils import TestCase; 29 | from cygapt.test.utils import SetupIniProvider; 30 | from cygapt.setup import PlatformException; 31 | from cygapt.setup import EnvironementException; 32 | from cygapt.exception import PathExistsException; 33 | from cygapt.exception import UnexpectedValueException; 34 | from cygapt.setup import SignatureException; 35 | from cygapt.ob import CygAptOb; 36 | from cygapt.process import Process; 37 | 38 | 39 | class TestSetup(TestCase): 40 | def setUp(self): 41 | TestCase.setUp(self); 42 | self._var_verbose = False; 43 | self._var_cygwin_p = ( 44 | sys.platform.startswith("cygwin") 45 | or sys.platform.startswith("linux") 46 | ); 47 | self.obj = CygAptSetup( 48 | self._var_cygwin_p, 49 | self._var_verbose, 50 | self._var_arch, 51 | ); 52 | self.obj.setTmpDir(self._dir_tmp); 53 | self.obj.setAppName(self._var_exename); 54 | self.obj.setSetupDir(self._dir_confsetup); 55 | self.obj.getRC().ROOT = self._dir_mtroot; 56 | 57 | def test__init__(self): 58 | self.assertTrue(isinstance(self.obj, CygAptSetup)); 59 | self.assertEqual(self.obj.getCygwinPlatform(), self._var_cygwin_p); 60 | self.assertEqual(self.obj.getVerbose(), self._var_verbose); 61 | 62 | def testGetSetupRc(self): 63 | badlocation = os.path.join(self._var_tmpdir, "not_exist_file"); 64 | last_cache, last_mirror = self.obj.getSetupRc(badlocation); 65 | self.assertEqual(last_cache, None); 66 | self.assertEqual(last_mirror, None); 67 | 68 | last_cache, last_mirror = self.obj.getSetupRc(self._dir_confsetup); 69 | self.assertEqual(last_cache, self._dir_execache); 70 | self.assertEqual(last_mirror, self._var_mirror); 71 | 72 | def testGetPre17Last(self): 73 | location = self._var_tmpdir; 74 | last_mirror = "http://cygwin.uib.no/"; 75 | last_cache = os.path.join(self._var_tmpdir, "last_cache"); 76 | os.mkdir(last_cache); 77 | lm_file = os.path.join(self._var_tmpdir, "last-mirror"); 78 | lc_file = os.path.join(self._var_tmpdir, "last-cache"); 79 | lm_stream = open(lm_file, 'w'); 80 | lm_stream.write(last_mirror); 81 | lm_stream.close(); 82 | lc_stream = open(lc_file, 'w'); 83 | lc_stream.write(last_cache); 84 | lc_stream.close(); 85 | 86 | rlast_cache, rlast_mirror = self.obj._getPre17Last(location); 87 | self.assertEqual(last_cache, rlast_cache); 88 | self.assertEqual(last_mirror, rlast_mirror); 89 | 90 | def testUpdateWithGoodMirrorSignature(self): 91 | if not self._var_cygwin_p: 92 | self.skipTest("requires cygwin or linux"); 93 | 94 | self._writeUserConfig(self._file_user_config); 95 | 96 | self.obj._gpgImport(self.obj.GPG_CYG_PUBLIC_RING_URI); 97 | self.obj.update(self._file_user_config, True, self._var_mirror_http); 98 | 99 | def testUpdateWithBadMirrorSignature(self): 100 | if not self._var_cygwin_p: 101 | self.skipTest("requires cygwin or linux"); 102 | 103 | self._writeUserConfig(self._file_user_config); 104 | self.obj._gpgImport(self.obj.GPG_CYG_PUBLIC_RING_URI); 105 | 106 | message = '^{0}$'.format(re.escape( 107 | "{0}{1}{2}/setup.bz2 not signed by Cygwin's public key." 108 | " Use -X to ignore signatures." 109 | "".format( 110 | self._var_mirror, 111 | '' if self._var_mirror.endswith('/') else '/', 112 | self._var_setupIni.getArchitecture(), 113 | ))); 114 | 115 | with self.assertRaisesRegexp(SignatureException, message): 116 | self.obj.update(self._file_user_config, True); 117 | 118 | @dataProvider('getUpdateWithoutVerifySignatureWithAnyEndSlashCountData') 119 | def testUpdateWithoutVerifySignatureWithAnyEndSlashCount(self, mirrorEndSlashCount): 120 | if not self._var_cygwin_p: 121 | self.skipTest("requires cygwin or linux"); 122 | 123 | self._var_mirror = self._var_mirror.rstrip('/')+'/'*mirrorEndSlashCount; 124 | self._writeUserConfig(self._file_user_config); 125 | 126 | self.obj.update(self._file_user_config, False); 127 | 128 | self._assertUpdate(); 129 | 130 | def getUpdateWithoutVerifySignatureWithAnyEndSlashCountData(self): 131 | return [ 132 | [0], 133 | [1], 134 | ]; 135 | 136 | @dataProvider('getUpdateWithoutVerifySignatureAndWithValidArchitectureData') 137 | def testUpdateWithoutVerifySignatureAndWithValidArchitecture(self, arch): 138 | if not self._var_cygwin_p: 139 | self.skipTest("requires cygwin or linux"); 140 | 141 | self._var_arch = arch; 142 | self._var_setupIni = SetupIniProvider(self, self._var_arch); 143 | self.obj.setArchitecture(self._var_arch); 144 | 145 | self._writeUserConfig(self._file_user_config); 146 | 147 | self.obj.update(self._file_user_config, False); 148 | 149 | self._assertUpdate(); 150 | 151 | def getUpdateWithoutVerifySignatureAndWithValidArchitectureData(self): 152 | return [ 153 | ["x86"], 154 | ["x86_64"], 155 | ]; 156 | 157 | def testUpdateWithoutMirror(self): 158 | if not self._var_cygwin_p: 159 | self.skipTest("requires cygwin or linux"); 160 | 161 | self._var_mirror = ""; 162 | self._writeUserConfig(self._file_user_config); 163 | 164 | message = '^{0}$'.format(re.escape( 165 | 'A mirror must be specified on the configuration file "{0}"' 166 | ' or with the command line option "--mirror".' 167 | ' See cygwin.com/mirrors.html for the list of mirrors.' 168 | ''.format(self._file_user_config), 169 | )); 170 | 171 | with self.assertRaisesRegexp(UnexpectedValueException, message): 172 | self.obj.update(self._file_user_config, False); 173 | 174 | def testUpdateWithSetupIniFieldWarnDeprecationWarning(self): 175 | if not self._var_cygwin_p: 176 | self.skipTest("requires cygwin"); 177 | 178 | self._writeUserConfig(self._file_user_config, keepBC=True); 179 | 180 | self._assertDeprecatedWarning( 181 | "The configuration field `setup_ini` is deprecated since version" 182 | " 1.1 and will be removed in 2.0.", 183 | self.obj.update, 184 | self._file_user_config, 185 | False, 186 | ); 187 | 188 | self._assertUpdate(keepBC=True); 189 | 190 | def testUpdateWithoutSetupIniFieldNotWarnDeprecationWarning(self): 191 | if not self._var_cygwin_p: 192 | self.skipTest("requires cygwin"); 193 | 194 | self._writeUserConfig(self._file_user_config); 195 | 196 | self._assertNotDeprecatedWarning( 197 | "The configuration field `setup_ini` is deprecated since version" 198 | " 1.1 and will be removed in 2.0.", 199 | self.obj.update, 200 | self._file_user_config, 201 | False, 202 | ); 203 | 204 | def testSetup(self): 205 | if not self._var_cygwin_p: 206 | self.assertRaises(PlatformException, self.obj.setup); 207 | return; 208 | 209 | # env HOME not exists 210 | os.environ.pop('HOME'); 211 | self.assertRaises(EnvironementException, self.obj.setup); 212 | os.environ['HOME'] = self._dir_user; 213 | 214 | # config file already isset 215 | f = open(self._file_user_config, 'w'); 216 | f.close(); 217 | self.assertRaises(PathExistsException, self.obj.setup); 218 | self.assertTrue(os.path.exists(self._file_user_config)); 219 | 220 | os.remove(self._file_user_config); 221 | 222 | # next 223 | # mirror end with one slash 224 | self._var_mirror = self._var_mirror_http.rstrip('/')+'/'; 225 | self._writeSetupRc(self._file_setup_rc); 226 | self.obj._gpgImport(self.obj.GPG_CYG_PUBLIC_RING_URI); 227 | self.obj.setup(); 228 | 229 | # create a default user configuration file 230 | self.assertTrue(os.path.isfile(self._file_user_config)); 231 | with open(self._file_user_config, 'r') as f : 232 | self.assertEqual("\n".join([ 233 | "# The distribution, current previous or test [curr, prev, test].", 234 | '# Usually you want the "curr" version of a package.', 235 | 'distname="curr"', 236 | "", 237 | "# Your package cache as a POSIX path: example /e/home/cygwin_package_cache", 238 | 'cache="{self[_dir_execache]}"', 239 | "", 240 | "# Packages which cyg-apt can't change under Cygwin since it depends on them.", 241 | "# Run cyg-apt under DOS with -f (force) option to change these packages.", 242 | "# Treat Cygwin core packages with CAUTION.", 243 | 'barred=""', 244 | "", 245 | "# URL of your Cygwin mirror: example http://mirror.internode.on.net/pub/cygwin/", 246 | 'mirror="{self[_var_mirror]}"', 247 | "", 248 | "# Always update setup.ini before any command that uses it. cyg-apt will be", 249 | "# faster and use less bandwidth if False but you will have to run the update", 250 | "# command manually.", 251 | 'always_update="False"', 252 | "", 253 | "# setup.ini lists available packages and is downloaded from the top level", 254 | "# of the downloaded mirror. Standard location is /etc/setup/setup.ini,", 255 | "# seutp-2.ini for Cygwin 1.7 Beta", 256 | "# Deprecated since version 1.1 and will be removed in 2.0.", 257 | '# setup_ini="{self[_file_setup_ini]}"', 258 | "", 259 | "# The root of your Cygwin installation as a windows path", 260 | 'ROOT="{self[_dir_mtroot]}"', 261 | "", 262 | "", 263 | ]).format(self=vars(self)), f.read()); 264 | 265 | # create setup.ini on `/etc/setup/` 266 | self.assertFalse(os.path.isfile(self._file_setup_ini)); 267 | 268 | # create setup.ini on `///` 269 | setupIniPath = os.path.join( 270 | self._getDownloadDir(), 271 | self._var_arch, 272 | "setup.ini", 273 | ); 274 | self.assertTrue(os.path.isfile(setupIniPath)); 275 | 276 | # mirror end without slash 277 | self._var_mirror = self._var_mirror_http.rstrip('/'); 278 | self._writeSetupRc(self._file_setup_rc); 279 | # fail if setupIniPath will be rewrite 280 | os.chmod(setupIniPath, 0o000); 281 | self.obj.setup(True); 282 | 283 | def testSetupNotWarnDeprecationWarning(self): 284 | if not self._var_cygwin_p: 285 | self.skipTest("requires cygwin"); 286 | 287 | self._var_mirror = self._var_mirror_http; 288 | self._writeSetupRc(self._file_setup_rc); 289 | self.obj._gpgImport(self.obj.GPG_CYG_PUBLIC_RING_URI); 290 | 291 | self._assertNotDeprecatedWarning( 292 | "The configuration field `setup_ini` is deprecated since version" 293 | " 1.1 and will be removed in 2.0.", 294 | self.obj.setup, 295 | ); 296 | 297 | def testWriteInstalled(self): 298 | if not sys.platform.startswith("cygwin"): 299 | self.skipTest("requires cygwin"); 300 | 301 | real_installed_db = self._file_installed_db.replace(self._var_tmpdir, ""); 302 | self.obj._writeInstalled(self._file_installed_db); 303 | self.assertTrue(os.path.exists(self._file_installed_db)); 304 | f = open(self._file_installed_db); 305 | ret = f.readlines(); 306 | ret.sort(); 307 | f.close(); 308 | f = open(real_installed_db); 309 | expected = f.readlines(); 310 | expected.sort(); 311 | f.close(); 312 | self.assertEqual(ret, expected); 313 | 314 | def testGpgImport(self): 315 | if not self._var_cygwin_p: 316 | self.skipTest("requires cygwin or linux"); 317 | 318 | self.obj._gpgImport(self.obj.GPG_CYG_PUBLIC_RING_URI); 319 | 320 | cmd = " ".join([ 321 | "gpg", 322 | "--no-secmem-warning", 323 | "--list-public-keys", 324 | "--fingerprint", 325 | ]); 326 | p = Process(cmd); 327 | p.mustRun(); 328 | lines = p.getOutput().splitlines(True); 329 | findout = False; 330 | for line in lines: 331 | if isinstance(line, bytes): 332 | marker = self.obj.GPG_GOOD_FINGER.encode(); 333 | else: 334 | marker = self.obj.GPG_GOOD_FINGER; 335 | if marker in line: 336 | findout = True; 337 | break; 338 | 339 | self.assertTrue(findout); 340 | 341 | def testUsage(self): 342 | self.obj.usage(); 343 | 344 | def testUsageNotContainMd5Command(self): 345 | try: 346 | self.testUsageContainCommand("md5"); 347 | except self.failureException : 348 | pass; 349 | else: 350 | self.fail("Failed asserting that usage does not contain md5 command."); 351 | 352 | @dataProvider('getUsageContainCommandData') 353 | def testUsageContainCommand(self, command): 354 | ob = CygAptOb(True); 355 | try: 356 | self.obj.usage(); 357 | finally: 358 | ret = ob.getClean(); 359 | 360 | self.assertTrue(" {0}".format(command) in ret); 361 | 362 | def getUsageContainCommandData(self): 363 | return [ 364 | ['postinstall'], 365 | ['postremove'], 366 | ['checksum'], 367 | ]; 368 | 369 | def _assertUpdate(self, keepBC=False): 370 | """Asserts that the local setup.ini has been updated. 371 | 372 | @raise AssertionError: When the assertion is not verify. 373 | """ 374 | onCache = os.path.join( 375 | self._getDownloadDir(), 376 | self._var_setupIni.getArchitecture(), 377 | "setup.ini" 378 | ); 379 | 380 | self.assertTrue(os.path.isfile(onCache), onCache+" not exists."); 381 | 382 | expected = self._var_setupIni.contents; 383 | 384 | with open(onCache, 'r') as f : 385 | actual = f.read(); 386 | self.assertEqual(expected, actual); 387 | 388 | if not keepBC : 389 | return; 390 | 391 | # BC layer for `setup_ini` configuration field 392 | onEtc = self._file_setup_ini; 393 | self.assertTrue(os.path.isfile(onEtc), onEtc+" not exists."); 394 | with open(onEtc, 'r') as f : 395 | actual = f.read(); 396 | self.assertEqual(expected, actual); 397 | 398 | if __name__ == "__main__": 399 | unittest.main() 400 | -------------------------------------------------------------------------------- /src/cygapt/test/test_url_opener.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | import unittest; 18 | import sys; 19 | from tempfile import TemporaryFile; 20 | from cStringIO import StringIO; 21 | 22 | from cygapt.test.case import TestCase; 23 | from cygapt.url_opener import CygAptURLopener; 24 | 25 | class TestUrlOpener(TestCase): 26 | """Unit test for cygapt.url_opener 27 | """ 28 | 29 | def setUp(self): 30 | TestCase.setUp(self); 31 | self.obj = CygAptURLopener(True); 32 | 33 | def test__init__(self): 34 | self.assertTrue(isinstance(self.obj, CygAptURLopener)); 35 | 36 | def testHttp_error_default(self): 37 | f = TemporaryFile(); 38 | errcode = 404; 39 | self.obj.http_error_default("url", f, errcode, "errmsg", "headers"); 40 | f.close(); 41 | self.assertEqual(self.obj.getErrorCode(), errcode); 42 | 43 | def testDlProgress(self): 44 | old_stdout = sys.stdout; 45 | buf = StringIO(); 46 | sys.stdout = buf; 47 | self.obj.dlProgress(1, 512, 1024); 48 | sys.stdout = old_stdout; 49 | buf.seek(0); 50 | out = buf.readline(); 51 | buf.close(); 52 | expect_out = "[====================> ]\r"; 53 | 54 | self.assertEqual(out, expect_out); 55 | 56 | if __name__ == "__main__": 57 | unittest.main(); 58 | -------------------------------------------------------------------------------- /src/cygapt/test/test_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | import unittest; 18 | import sys; 19 | import os; 20 | import stat; 21 | import re; 22 | 23 | import cygapt.utils as utils; 24 | from cygapt.exception import InvalidArgumentException; 25 | from cygapt.exception import UnexpectedValueException; 26 | from cygapt.test.utils import TestCase; 27 | from cygapt.structure import ConfigStructure; 28 | from cygapt.test.case import dataProvider; 29 | from cygapt.test.case import expectedException; 30 | 31 | __DIR__ = os.path.dirname(os.path.realpath(os.path.abspath(__file__))); 32 | 33 | class TestUtils(TestCase): 34 | def _getTmpDir(self): 35 | return self._dir_tmp; 36 | 37 | def _getTmpFileName(self): 38 | filename = "{0}{1}test~".format(self._getTmpDir(), os.path.sep); 39 | return filename; 40 | 41 | def testCygpath(self): 42 | if not sys.platform.startswith("cygwin"): 43 | self.skipTest("requires cygwin"); 44 | 45 | good_paths = ["/", ".", "./", "./.", "/bin"]; 46 | 47 | for path in good_paths: 48 | ret = utils.cygpath(path); 49 | self.assertEquals(ret, path); 50 | 51 | def testParseRc(self): 52 | self._writeUserConfig(self._getTmpFileName()); 53 | ret = utils.parse_rc(self._getTmpFileName()); 54 | self.assertTrue(isinstance(ret, ConfigStructure)); 55 | self.assertFalse(ret.always_update); 56 | self.assertEqual(ret.ROOT, self._dir_mtroot); 57 | self.assertEqual(ret.mirror, self._var_mirror); 58 | self.assertEqual(ret.cache, self._dir_execache); 59 | self.assertEqual(ret.distname, 'curr'); 60 | self.assertEqual(ret.barred, ''); 61 | 62 | @dataProvider('getParseRcWithOneLineData') 63 | def testParseRcBooleanValue(self, value, expected): 64 | with open(self._getTmpFileName(), 'w') as f: 65 | f.write("always_update = {0}".format(value)); 66 | 67 | ret = utils.parse_rc(self._getTmpFileName()); 68 | self.assertTrue(isinstance(ret, ConfigStructure)); 69 | self.assertEqual(ret.always_update, expected); 70 | 71 | def getParseRcWithOneLineData(self): 72 | return [ 73 | ['True', True], 74 | ['"True"', True], 75 | ['"true"', True], 76 | ['"Yes"', True], 77 | ['"yes"', True], 78 | ['False', False], 79 | ['"False"', False], 80 | ['parse_rc(cyg_apt_rc)', False], # malicious code 81 | ]; 82 | 83 | @dataProvider('getParseRcWithTrailingWhitespaceData') 84 | def testParseRcWithTrailingWhitespace(self, whitespace): 85 | f = open(self._getTmpFileName(), 'w'); 86 | f.write("mirror = \"http://foo\""+whitespace); 87 | f.close(); 88 | ret = utils.parse_rc(self._getTmpFileName()); 89 | self.assertTrue(isinstance(ret, ConfigStructure)); 90 | self.assertEqual("http://foo", ret.mirror); 91 | 92 | def getParseRcWithTrailingWhitespaceData(self): 93 | return [ 94 | [' '], 95 | ['\r'], 96 | ['\t'], 97 | [' \r\t\r\n'], 98 | ]; 99 | 100 | def testPrsort(self): 101 | in_lst = ["B", "A", "a", "1", "b", "/", "", "2"]; 102 | 103 | out_lst = ["b", "a", "B", "A", "2", "1", "/", ""]; 104 | 105 | utils.prsort(in_lst); 106 | self.assertEqual(in_lst, out_lst); 107 | 108 | def testRename(self): 109 | dest = self._getTmpFileName(); 110 | src = "{0}2".format(self._getTmpFileName()); 111 | stream = open(src, 'w+'); 112 | stream.writelines("1"); 113 | stream.seek(0); 114 | src_content = stream.readlines(); 115 | stream.close(); 116 | 117 | stream = open(dest, 'w'); 118 | stream.writelines("2"); 119 | stream.close(); 120 | 121 | utils.rename(src, dest); 122 | 123 | stream = open(dest, 'r'); 124 | res_dest_content = stream.readlines(); 125 | stream.close(); 126 | 127 | self.assertFalse(os.path.exists(src)); 128 | self.assertEqual(src_content, res_dest_content); 129 | 130 | if os.path.exists(src): 131 | os.unlink(src); 132 | 133 | def testRmtreeCleansFilesAndDirectories(self): 134 | basePath = self._getTmpDir()+os.path.sep; 135 | 136 | os.mkdir(basePath+"dir"); 137 | open(basePath+"file", 'w').close(); 138 | 139 | utils.rmtree(basePath+"dir"); 140 | utils.rmtree(basePath+"file"); 141 | 142 | self.assertFalse(os.path.isdir(basePath+"dir")); 143 | self.assertFalse(os.path.isfile(basePath+"file")); 144 | 145 | def testRmtreeCleansFilesAndDirectoriesIteratively(self): 146 | basePath = os.path.join(self._getTmpDir(), "directory")+os.path.sep; 147 | 148 | os.mkdir(basePath); 149 | os.mkdir(basePath+"dir"); 150 | open(basePath+"file", 'w').close(); 151 | 152 | utils.rmtree(basePath); 153 | 154 | self.assertFalse(os.path.isdir(basePath)); 155 | 156 | def testRmtreeCleansWithoutPermission(self): 157 | basePath = os.path.join(self._getTmpDir(), "directory")+os.path.sep; 158 | 159 | os.mkdir(basePath); 160 | os.mkdir(basePath+"dir"); 161 | open(basePath+"file", 'w').close(); 162 | 163 | # Removes permissions 164 | os.chmod(basePath+"dir", 0o000); 165 | os.chmod(basePath+"file", 0o000); 166 | 167 | utils.rmtree(basePath+"dir"); 168 | utils.rmtree(basePath+"file"); 169 | 170 | self.assertFalse(os.path.isdir(basePath+"dir")); 171 | self.assertFalse(os.path.isfile(basePath+"file")); 172 | 173 | def testRmtreeCleansWithoutPermissionIteratively(self): 174 | basePath = os.path.join(self._getTmpDir(), "directory")+os.path.sep; 175 | 176 | os.mkdir(basePath); 177 | os.mkdir(basePath+"dir"); 178 | open(basePath+"file", 'w').close(); 179 | 180 | # Removes permissions 181 | os.chmod(basePath+"dir", 0o000); 182 | os.chmod(basePath+"file", 0o000); 183 | 184 | utils.rmtree(basePath); 185 | 186 | self.assertFalse(os.path.isdir(basePath)); 187 | 188 | def testRmtreeIgnoresNonExistingFiles(self): 189 | basePath = self._getTmpDir()+os.path.sep; 190 | 191 | os.mkdir(basePath+"dir"); 192 | 193 | utils.rmtree(basePath+"dir"); 194 | utils.rmtree(basePath+"file"); 195 | 196 | self.assertFalse(os.path.isdir(basePath+"dir")); 197 | 198 | def testRmtreeCleansValidLinksToFile(self): 199 | if not hasattr(os, "symlink") : 200 | self.skipTest("symlink is not supported"); 201 | 202 | basePath = self._getTmpDir()+os.path.sep; 203 | 204 | open(basePath+"file", 'w').close(); 205 | os.symlink(basePath+"file", basePath+"link"); 206 | 207 | utils.rmtree(basePath+"link"); 208 | 209 | self.assertTrue(os.path.isfile(basePath+"file")); 210 | self.assertFalse(os.path.islink(basePath+"link")); 211 | 212 | def testRmtreeCleansValidLinksToFileIteratively(self): 213 | if not hasattr(os, "symlink") : 214 | self.skipTest("symlink is not supported"); 215 | 216 | basePath = self._getTmpDir()+os.path.sep; 217 | 218 | os.mkdir(basePath+"dir"); 219 | open(basePath+"file", 'w').close(); 220 | os.symlink(basePath+"file", basePath+"dir"+os.path.sep+"link"); 221 | 222 | utils.rmtree(basePath+"dir"); 223 | 224 | self.assertTrue(os.path.isfile(basePath+"file")); 225 | self.assertFalse(os.path.isdir(basePath+"dir")); 226 | 227 | def testRmtreeKeepsTargetLinkPermissionsToFile(self): 228 | if not hasattr(os, "symlink") : 229 | self.skipTest("symlink is not supported"); 230 | 231 | basePath = self._getTmpDir()+os.path.sep; 232 | 233 | open(basePath+"file", 'w').close(); 234 | os.symlink(basePath+"file", basePath+"link"); 235 | 236 | # Removes permissions 237 | os.chmod(basePath+"file", 0o000); 238 | fileMode = os.stat(basePath+"file")[stat.ST_MODE]; 239 | 240 | utils.rmtree(basePath+"link"); 241 | 242 | self.assertTrue(os.path.isfile(basePath+"file")); 243 | self.assertEqual(fileMode, os.stat(basePath+"file")[stat.ST_MODE]); 244 | 245 | def testRmtreeKeepsTargetLinkPermissionsToFileIteratively(self): 246 | if not hasattr(os, "symlink") : 247 | self.skipTest("symlink is not supported"); 248 | 249 | basePath = self._getTmpDir()+os.path.sep; 250 | 251 | os.mkdir(basePath+"dir"); 252 | open(basePath+"file", 'w').close(); 253 | os.symlink(basePath+"file", basePath+"dir"+os.path.sep+"link"); 254 | 255 | # Removes permissions 256 | os.chmod(basePath+"file", 0o000); 257 | fileMode = os.stat(basePath+"file")[stat.ST_MODE]; 258 | 259 | utils.rmtree(basePath+"dir"); 260 | 261 | self.assertTrue(os.path.isfile(basePath+"file")); 262 | self.assertEqual(fileMode, os.stat(basePath+"file")[stat.ST_MODE]); 263 | 264 | def testRmtreeCleansValidLinksToDirectory(self): 265 | if not hasattr(os, "symlink") : 266 | self.skipTest("symlink is not supported"); 267 | 268 | basePath = self._getTmpDir()+os.path.sep; 269 | 270 | os.mkdir(basePath+"dir"); 271 | os.symlink(basePath+"dir", basePath+"link"); 272 | 273 | utils.rmtree(basePath+"link"); 274 | 275 | self.assertTrue(os.path.isdir(basePath+"dir")); 276 | self.assertFalse(os.path.islink(basePath+"link")); 277 | 278 | def testRmtreeCleansValidLinksToDirectoryIteratively(self): 279 | if not hasattr(os, "symlink") : 280 | self.skipTest("symlink is not supported"); 281 | 282 | basePath = self._getTmpDir()+os.path.sep; 283 | 284 | os.mkdir(basePath+"dir"); 285 | os.mkdir(basePath+"dir2"); 286 | os.symlink(basePath+"dir2", basePath+"dir"+os.path.sep+"link"); 287 | 288 | utils.rmtree(basePath+"dir"); 289 | 290 | self.assertTrue(os.path.isdir(basePath+"dir2")); 291 | self.assertFalse(os.path.isdir(basePath+"dir")); 292 | 293 | def testRmtreeKeepsTargetLinkPermissionsToDirectory(self): 294 | if not hasattr(os, "symlink") : 295 | self.skipTest("symlink is not supported"); 296 | 297 | basePath = self._getTmpDir()+os.path.sep; 298 | 299 | os.mkdir(basePath+"dir"); 300 | os.symlink(basePath+"dir", basePath+"link"); 301 | 302 | # Removes permissions 303 | os.chmod(basePath+"dir", 0o000); 304 | fileMode = os.stat(basePath+"dir")[stat.ST_MODE]; 305 | 306 | utils.rmtree(basePath+"link"); 307 | 308 | self.assertTrue(os.path.isdir(basePath+"dir")); 309 | self.assertEqual(fileMode, os.stat(basePath+"dir")[stat.ST_MODE]); 310 | 311 | def testRmtreeKeepsTargetLinkPermissionsToDirectoryIteratively(self): 312 | if not hasattr(os, "symlink") : 313 | self.skipTest("symlink is not supported"); 314 | 315 | basePath = self._getTmpDir()+os.path.sep; 316 | 317 | os.mkdir(basePath+"dir"); 318 | os.mkdir(basePath+"dir2"); 319 | os.symlink(basePath+"dir2", basePath+"dir"+os.path.sep+"link"); 320 | 321 | # Removes permissions 322 | os.chmod(basePath+"dir2", 0o000); 323 | fileMode = os.stat(basePath+"dir2")[stat.ST_MODE]; 324 | 325 | utils.rmtree(basePath+"dir"); 326 | 327 | self.assertTrue(os.path.isdir(basePath+"dir2")); 328 | self.assertEqual(fileMode, os.stat(basePath+"dir2")[stat.ST_MODE]); 329 | 330 | def testRmtreeCleansInvalidLinks(self): 331 | if not hasattr(os, "symlink") : 332 | self.skipTest("symlink is not supported"); 333 | 334 | basePath = self._getTmpDir()+os.path.sep; 335 | 336 | # create symlink to unexisting file 337 | os.symlink(basePath+"file", basePath+"link"); 338 | 339 | utils.rmtree(basePath+"link"); 340 | 341 | self.assertFalse(os.path.islink(basePath+"link")); 342 | 343 | def testRmtreeCleansInvalidLinksIteratively(self): 344 | if not hasattr(os, "symlink") : 345 | self.skipTest("symlink is not supported"); 346 | 347 | basePath = os.path.join(self._getTmpDir(), "directory")+os.path.sep; 348 | 349 | os.mkdir(basePath); 350 | os.mkdir(basePath+"dir"); 351 | 352 | # create symlink to unexisting file 353 | os.symlink(basePath+"file", basePath+"link"); 354 | 355 | utils.rmtree(basePath); 356 | 357 | self.assertFalse(os.path.isdir(basePath)); 358 | 359 | @dataProvider('getUriGetData') 360 | def testUriGet(self, uri, protocol): 361 | directory = self._getTmpDir(); 362 | targetPath = os.path.join(directory, os.path.basename(uri)); 363 | 364 | try: 365 | utils.uri_get(directory, uri, False); 366 | except utils.RequestException: 367 | self.skipTest("Your network doesn't allow {0} requests.".format(protocol)); 368 | 369 | self.assertTrue(os.path.exists(targetPath)); 370 | 371 | def getUriGetData(self): 372 | return [ 373 | ['http://cygwin.uib.no/x86/setup.bz2.sig', 'HTTP'], 374 | ['ftp://cygwin.uib.no/pub/cygwin/x86/setup.ini.sig', 'FTP'], 375 | ]; 376 | 377 | @dataProvider('getUriGetWithInvalidSchemeData') 378 | @expectedException(InvalidArgumentException) 379 | def testUriGetWithInvalidScheme(self, uri): 380 | utils.uri_get(self._getTmpDir(), uri, False); 381 | 382 | def getUriGetWithInvalidSchemeData(self): 383 | return [ 384 | ['rsync://cygwin.uib.no/cygwin/x86/setup-legacy.bz2.sig'], 385 | [''], 386 | ]; 387 | 388 | @dataProvider('getOpenTarfileData') 389 | def testOpenTarfile(self, package): 390 | self._successOpenTarfile(package); 391 | 392 | def getOpenTarfileData(self): 393 | return [ 394 | ['pkg'], 395 | ['pkgxz'], 396 | ]; 397 | 398 | def testOpenTarfileFromLZMABallWithoutPATH(self): 399 | if not sys.platform.startswith("cygwin") and not sys.platform.startswith("linux") : 400 | self.skipTest("requires cygwin or linux"); 401 | 402 | old_path = os.environ['PATH']; 403 | 404 | try: 405 | os.environ['PATH'] = ""; 406 | self._successOpenTarfile("pkgxz", "/usr/bin/xz"); 407 | finally: 408 | os.environ['PATH'] = old_path; 409 | 410 | def _successOpenTarfile(self, pkgname, xzPath="xz"): 411 | ball = os.path.join( 412 | self._dir_mirror, 413 | self._var_setupIni.__dict__[pkgname].install.curr.url, 414 | ); 415 | 416 | filesOnBallDir = os.listdir(os.path.dirname(ball)); 417 | 418 | tf = utils.open_tarfile(ball, xzPath); 419 | members = tf.getmembers(); 420 | tf.close(); 421 | 422 | self.assertEqual(filesOnBallDir, os.listdir(os.path.dirname(ball))); 423 | 424 | filelist = []; 425 | for member in members : 426 | path = member.name; 427 | if member.isdir() : 428 | path = path.rstrip("/")+"/"; 429 | filelist.append(path); 430 | 431 | self.assertEqual(sorted(filelist), sorted(self._var_setupIni.__dict__[pkgname].filelist)); 432 | 433 | @dataProvider('getPEArchitectureData') 434 | def testPEArchitecture(self, filename, expected): 435 | fn = os.path.join(__DIR__, 'fixtures', 'utils', filename); 436 | 437 | self.assertTrue(utils.pe_is_64_bit(fn) is expected); 438 | 439 | def getPEArchitectureData(self): 440 | return [ 441 | ['cyglsa.dll', False], 442 | ['cyglsa64.dll', True], 443 | ]; 444 | 445 | @dataProvider('getPEArchitectureInvalidData') 446 | def testPEArchitectureRaisesWithInvalidFile(self, filename, expectedMessage): 447 | fn = os.path.join(__DIR__, 'fixtures', 'utils', filename); 448 | message = '^{0}$'.format(re.escape(expectedMessage.format(fn))); 449 | 450 | with self.assertRaisesRegexp(UnexpectedValueException, message): 451 | utils.pe_is_64_bit(fn); 452 | 453 | def getPEArchitectureInvalidData(self): 454 | return [ 455 | ['cyglsa-bad1.dll', "File '{0}' is not a DOS executable."], 456 | ['cyglsa-bad2.dll', "Could not find PE header in file '{0}'."], 457 | ['cyglsa-bad3.dll', "Bad machine value 0xDEAD in file '{0}'."], 458 | ['cyglsa-bad4.dll', "Could not find PE header in file '{0}'."], 459 | ]; 460 | 461 | if __name__ == "__main__": 462 | unittest.main(); 463 | -------------------------------------------------------------------------------- /src/cygapt/test/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import absolute_import; 15 | 16 | import os; 17 | import tempfile; 18 | import urllib; 19 | import tarfile; 20 | import bz2; 21 | import hashlib; 22 | import atexit; 23 | import time; 24 | 25 | from cygapt.test.case import TestCase as BaseTestCase; 26 | from cygapt.process import Process; 27 | 28 | class TestCase(BaseTestCase): 29 | __mirrorDir = None; 30 | 31 | def setUp(self): 32 | BaseTestCase.setUp(self); 33 | 34 | self._var_tmpdir = tempfile.mkdtemp(); 35 | self._var_old_cwd = os.getcwd(); 36 | os.chdir(self._var_tmpdir); 37 | self._var_exename = "cyg-apt"; 38 | self._var_old_env = os.environ; 39 | self._var_arch = "x86"; 40 | 41 | # unix tree 42 | self._dir_mtroot = "{0}/".format(self._var_tmpdir); 43 | self._dir_tmp = os.path.join(self._dir_mtroot, "tmp"); 44 | self._dir_prefix = os.path.join(self._dir_mtroot, "usr"); 45 | self._dir_bin = os.path.join(self._dir_prefix, "bin"); 46 | self._dir_sysconf = os.path.join(self._dir_mtroot, "etc"); 47 | self._dir_localstate = os.path.join(self._dir_mtroot, "var"); 48 | self._dir_home = os.path.join(self._dir_mtroot, "home"); 49 | self._dir_libexec = os.path.join(self._dir_prefix, "lib"); 50 | self._dir_data = os.path.join(self._dir_prefix, "share"); 51 | self._dir_man = os.path.join(self._dir_data, "man"); 52 | self._dir_info = os.path.join(self._dir_data, "info"); 53 | self._dir_postinstall = os.path.join(self._dir_sysconf, "postinstall"); 54 | self._dir_preremove = os.path.join(self._dir_sysconf, "preremove"); 55 | self._dir_postremove = os.path.join(self._dir_sysconf, "postremove"); 56 | 57 | # bulld unix tree 58 | os.mkdir(self._dir_tmp); 59 | os.mkdir(self._dir_prefix); 60 | os.mkdir(self._dir_bin); 61 | os.mkdir(self._dir_sysconf); 62 | os.mkdir(self._dir_localstate); 63 | os.mkdir(self._dir_home); 64 | os.mkdir(self._dir_libexec); 65 | os.mkdir(self._dir_data); 66 | os.mkdir(self._dir_man); 67 | os.mkdir(self._dir_info); 68 | 69 | self._dir_mirror = self.__getMirrorDir(); 70 | self._var_mirror = "file://{0}".format(self._dir_mirror); 71 | self._var_mirror_http = "http://cygwin.uib.no/"; 72 | 73 | # exe tree 74 | self._dir_confsetup = os.path.join(self._dir_sysconf, "setup"); 75 | self._dir_user = os.path.join(self._dir_home, "user"); 76 | self._dir_execache = os.path.join( 77 | self._dir_localstate, 78 | "cache", 79 | self._var_exename 80 | ); 81 | self._dir_exedata = os.path.join(self._dir_data, self._var_exename); 82 | 83 | # build exe tree 84 | os.mkdir(self._dir_confsetup); 85 | os.mkdir(self._dir_user); 86 | os.mkdir(self._dir_exedata); 87 | 88 | # exe files 89 | self._file_cygwin_sig = os.path.join( 90 | self._dir_exedata, 91 | "cygwin.sig" 92 | ); 93 | self._file_installed_db = os.path.join( 94 | self._dir_confsetup, 95 | "installed.db" 96 | ); 97 | 98 | # BC layer for `setup_ini` configuration field 99 | self._file_setup_ini = os.path.join( 100 | self._dir_confsetup, 101 | "setup.ini" 102 | ); 103 | 104 | self._file_setup_rc = os.path.join( 105 | self._dir_confsetup, 106 | "setup.rc" 107 | ); 108 | self._file_user_config = os.path.join( 109 | self._dir_user, 110 | ".{0}".format(self._var_exename) 111 | ); 112 | 113 | self._writeSetupRc(); 114 | 115 | os.environ['TMP'] = self._dir_tmp; 116 | os.environ['HOME'] = self._dir_user; 117 | 118 | os.chdir(self._dir_user); 119 | 120 | # build the mirror 121 | self._var_setupIni = SetupIniProvider(self, self._var_arch); 122 | 123 | def tearDown(self): 124 | BaseTestCase.tearDown(self); 125 | 126 | os.environ = self._var_old_env; 127 | os.chdir(self._var_old_cwd); 128 | self.__rmtree(self._var_tmpdir); 129 | 130 | def _writeSetupRc(self, path=None): 131 | if None is path : 132 | path = self._file_setup_rc; 133 | 134 | f = open(path, 'w'); 135 | f.write( 136 | "last-cache{LF}" 137 | "{2}{0}{LF}" 138 | "mirrors-lst{LF}" 139 | "{2}http://mirrors.163.com/cygwin/;mirrors.163.com;Asia;China{LF}" 140 | "{2}http://cygwin.uib.no/;cygwin.uib.no;Europe;Norway{LF}" 141 | "new-cygwin-version{LF}" 142 | "{2}1{LF}" 143 | "avahi{LF}" 144 | "{2}1{LF}" 145 | "mDNSResponder{LF}" 146 | "{2}1{LF}" 147 | "chooser_window_settings{LF}" 148 | "{2}44,2,3,4294935296,4294935296,4294967295,4294967295,371,316,909,709{LF}" 149 | "last-mirror{LF}" 150 | "{2}{1}{LF}" 151 | "net-method{LF}" 152 | "{2}Direct{LF}" 153 | "last-action{LF}" 154 | "{2}Download,Install{LF}" 155 | "".format( 156 | self._dir_execache, 157 | self._var_mirror, 158 | "\t", 159 | LF="\n" 160 | )); 161 | f.close(); 162 | 163 | def _writeUserConfig(self, path=None, keepBC=False): 164 | if None is path : 165 | path = self._file_user_config; 166 | 167 | f = open(path, 'w'); 168 | f.write("\n".join([ 169 | "ROOT='{self[_dir_mtroot]}'", 170 | "mirror='{self[_var_mirror]}'", 171 | "cache='{self[_dir_execache]}'", 172 | 173 | # BC layer for `setup_ini` configuration field 174 | "setup_ini='{self[_file_setup_ini]}'" if keepBC else "", 175 | 176 | "distname='curr'", 177 | "barred=''", 178 | "always_update='False'", 179 | "", 180 | ]).format(self=vars(self))); 181 | f.close(); 182 | 183 | def _writeSetupIni(self, keepBC=False): 184 | """Updates the `setup.ini` file for the current configuration. 185 | 186 | It makes the same result that the `update` command. 187 | """ 188 | setupIniDir = os.path.join(self._getDownloadDir(), self._var_arch); 189 | setupIni = os.path.join(setupIniDir, "setup.ini"); 190 | 191 | if not os.path.isdir(setupIniDir) : 192 | os.makedirs(setupIniDir); 193 | 194 | with open(setupIni, 'w') as f : 195 | f.write(self._var_setupIni.contents); 196 | 197 | if not keepBC : 198 | return; 199 | 200 | # BC layer for `setup_ini` configuration field 201 | with open(self._file_setup_ini, 'w') as f : 202 | f.write(self._var_setupIni.contents); 203 | 204 | def _getDownloadDir(self): 205 | """Gets the download directory from the mirror 206 | 207 | @return: str 208 | """ 209 | sep = '' if self._var_mirror.endswith('/') else '/'; 210 | 211 | return os.path.join( 212 | self._dir_execache, 213 | urllib.quote(self._var_mirror+sep, '').lower(), 214 | ); 215 | 216 | def _writeScript(self, path, exitCode=0): 217 | """Writes sh script to path. 218 | 219 | @param path: str A file to write the sript. 220 | @param exitCode: integer The exit code of the script. 221 | """ 222 | directory = os.path.dirname(path); 223 | if not os.path.isdir(directory) : 224 | os.makedirs(directory); 225 | 226 | extension = os.path.splitext(path)[1]; 227 | content = ''; 228 | 229 | if '.sh' == extension : 230 | content = """\ 231 | #!/bin/sh 232 | 233 | # Checks whether this script has been executed by bash 234 | case $BASH_VERSION in #( 235 | "") exit 1;; #( 236 | *) :;; 237 | esac 238 | 239 | exit {0:d} 240 | """; 241 | 242 | if '.dash' == extension : 243 | content = """\ 244 | #!/bin/dash 245 | 246 | # Checks whether this script has not been executed by bash 247 | case $BASH_VERSION in #( 248 | "") :;; #( 249 | *) exit 1;; 250 | esac 251 | 252 | exit {0:d} 253 | """; 254 | 255 | if extension in ['.cmd', '.bat'] : 256 | content = """\ 257 | @echo off 258 | 259 | exit {0:d} 260 | """; 261 | 262 | content = content.format(exitCode); 263 | 264 | # use binary mode to force newlines 265 | with open(path, 'wb') as f : 266 | f.write(content); 267 | 268 | @classmethod 269 | def __getMirrorDir(cls): 270 | """Gets the mirror directory. 271 | 272 | @return: str The mirror directory. 273 | """ 274 | if None is not TestCase.__mirrorDir : 275 | return TestCase.__mirrorDir; 276 | 277 | tmpDir = tempfile.mkdtemp(); 278 | TestCase.__mirrorDir = os.path.join(tmpDir, "mirror"); 279 | 280 | atexit.register(cls.__rmtree, tmpDir); 281 | 282 | return TestCase.__mirrorDir; 283 | 284 | @classmethod 285 | def __rmtree(cls, path): 286 | """Removes a directory. 287 | 288 | @param path: str A path to a directory. 289 | """ 290 | if not os.path.isdir(path) : 291 | return; 292 | 293 | if os.path.islink(path) : 294 | os.remove(path); 295 | 296 | return; 297 | 298 | files = os.listdir(path); 299 | for filename in files: 300 | subpath = os.path.join(path, filename); 301 | if not os.path.islink(subpath) : 302 | os.chmod(subpath, 0o700); 303 | if os.path.isdir(subpath): 304 | cls.__rmtree(subpath); 305 | else: 306 | os.remove(subpath); 307 | os.rmdir(path); 308 | 309 | class SetupIniProvider(): 310 | """Create a fictif setup.ini""" 311 | def __init__(self, app, architecture="x86"): 312 | assert isinstance(app, TestCase); 313 | 314 | self.dists = DistNameStruct(); 315 | 316 | self._architecture = architecture; 317 | self._localMirror = os.path.join(app._dir_mirror, self._architecture); 318 | 319 | isBuilt = os.path.isdir(self._localMirror); 320 | 321 | packages = [ 322 | PackageIni(app, self._architecture, name="libpkg"), 323 | PackageIni(app, self._architecture, name="pkg", requires="libpkg"), 324 | PackageIni(app, self._architecture, name="libbarredpkg"), 325 | PackageIni(app, self._architecture, name="barredpkg", requires="libbarredpkg"), 326 | PackageIni(app, self._architecture, name="pkgxz", compression="xz"), 327 | PackageIni(app, self._architecture, name="sha256pkg", hashAlgo="sha256"), 328 | PackageIni(app, self._architecture, name="sha512pkg", hashAlgo="sha512"), 329 | PackageIni(app, self._architecture, name="dashpkg", scriptsExt=".dash"), 330 | PackageIni(app, self._architecture, name="batpkg", scriptsExt=".bat"), 331 | PackageIni(app, self._architecture, name="cmdpkg", scriptsExt=".cmd"), 332 | ]; 333 | 334 | for package in packages : 335 | name = package.name; 336 | self.__dict__[name] = package; 337 | for distname in self.dists.__dict__: 338 | if None is self.dists.__dict__[distname] : 339 | self.dists.__dict__[distname] = dict(); 340 | self.dists.__dict__[distname][name] = package.dists.__dict__[distname].__dict__; 341 | 342 | if isBuilt : 343 | with open(os.path.join(self._localMirror, "setup.ini"), 'r' ) as f : 344 | self.contents = f.read(); 345 | 346 | return; 347 | 348 | if not os.path.isdir(self._localMirror) : 349 | os.makedirs(self._localMirror); 350 | 351 | self.contents = "\n".join([ 352 | "# This file is automatically generated. If you edit it, your", 353 | "# edits will be discarded next time the file is generated.", 354 | "# See http://cygwin.com/setup.html for details.", 355 | "#", 356 | "release: cygapt.test", 357 | "arch: {0}", 358 | "setup-timestamp: {1}", 359 | "setup-version: 2.850", 360 | "", 361 | ]).format( 362 | self._architecture, 363 | int(time.time()), 364 | ); 365 | 366 | for package in packages : 367 | self.contents += "\n".join([ 368 | "", 369 | package.ini_contents, 370 | "", 371 | ]); 372 | 373 | self._buildMirror(); 374 | 375 | def getArchitecture(self): 376 | return self._architecture; 377 | 378 | def _buildMirror(self): 379 | setup_ini = os.path.join(self._localMirror, "setup.ini"); 380 | setup_bz2 = os.path.join(self._localMirror, "setup.bz2"); 381 | 382 | f = open(setup_ini, 'w'); 383 | f.write(self.contents); 384 | f.close(); 385 | 386 | open(setup_ini+".sig", 'w').close(); 387 | 388 | compressed = bz2.compress(self.contents.encode()); 389 | f = open(setup_bz2, 'wb'); 390 | f.write(compressed); 391 | f.close(); 392 | 393 | open(setup_bz2+".sig", 'w').close(); 394 | 395 | # Add a README file for the mirror 396 | readmePath = os.path.join(self._localMirror, "README.md"); 397 | f = open(readmePath, 'w'); 398 | f.write("\n".join([ 399 | "The mirror has been auto-generated by the `cygapt.test.utils.TestCase` class.", 400 | "", 401 | "For update it just delete the parent directory of the current file.", 402 | "", 403 | ])); 404 | f.close(); 405 | 406 | class PackageIni(): 407 | def __init__(self, app, arch, name="testpkg", category="test", requires="", compression="bz2", hashAlgo="md5", scriptsExt='.sh'): 408 | assert isinstance(app, TestCase); 409 | 410 | self._localMirror = app._dir_mirror; 411 | self._tmpdir = app._dir_tmp; 412 | self._writeScript = app._writeScript; 413 | self._compression = compression; 414 | self._hashAlgo = hashAlgo; 415 | self._scriptsExt = scriptsExt; 416 | self._generated = False; 417 | 418 | self.name = name; 419 | self.category = category; 420 | self.requires = requires; 421 | 422 | self.shortDesc = "\"Short description for {0}\"".format(self.name); 423 | self.longDesc = "\"Long description\nfor {0}\"".format(self.name); 424 | 425 | self.pkgPath = os.path.join(arch, "test", self.name); 426 | 427 | self.filelist = []; 428 | 429 | self.install = DistNameStruct(); 430 | self.install.curr = FileStruct(); 431 | self.install.prev = FileStruct(); 432 | self.install.test = FileStruct(); 433 | 434 | self.source = DistNameStruct(); 435 | self.source.curr = FileStruct(); 436 | self.source.prev = FileStruct(); 437 | self.source.test = FileStruct(); 438 | 439 | self.version = DistNameStruct(); 440 | self.ini_contents = ""; 441 | self.dists = DistsStruct(); 442 | 443 | self.build(); 444 | 445 | def build(self): 446 | mirror_pkg_dir = os.path.join(self._localMirror, self.pkgPath); 447 | self._generated = os.path.exists(mirror_pkg_dir); 448 | if not self._generated : 449 | os.makedirs(mirror_pkg_dir); 450 | 451 | self._buildDist(); 452 | self._buildPkg(); 453 | self._buildDists(); 454 | self._buildIniContents(); 455 | 456 | def _buildDist(self): 457 | self.version.prev = "1.0.1-1"; 458 | self.version.curr = "2.0.1-1"; 459 | self.version.test = "3.0.1-1"; 460 | 461 | for distname in self.install.__dict__: 462 | tarball = "{0}-{1}.tar.{2}".format( 463 | self.name, 464 | self.version.__dict__[distname], 465 | self._compression, 466 | ); 467 | self.install.__dict__[distname].url = os.path.join( 468 | self.pkgPath, 469 | tarball 470 | ); 471 | 472 | for distname in self.source.__dict__: 473 | srctarball = "{0}-{1}.src.tar.{2}".format( 474 | self.name, 475 | self.version.__dict__[distname], 476 | self._compression, 477 | ); 478 | self.source.__dict__[distname].url = os.path.join( 479 | self.pkgPath, 480 | srctarball 481 | ); 482 | 483 | def _buildIniContents(self): 484 | self.ini_contents = ( 485 | "@ {self[name]}{LF}" 486 | "sdesc: {self[shortDesc]}{LF}" 487 | "ldesc: {self[longDesc]}{LF}" 488 | "category: {self[category]}{LF}" 489 | "requires: {self[requires]}{LF}" 490 | "version: {self[version][curr]}{LF}" 491 | "install: {self[install][curr]}{LF}" 492 | "source: {self[source][curr]}{LF}" 493 | "[prev]{LF}" 494 | "version: {self[version][prev]}{LF}" 495 | "install: {self[install][prev]}{LF}" 496 | "source: {self[source][prev]}{LF}" 497 | "[test]{LF}" 498 | "version: {self[version][test]}{LF}" 499 | "install: {self[install][test]}{LF}" 500 | "source: {self[source][test]}" 501 | "".format(self=vars(self), LF="\n") 502 | ); 503 | 504 | def _buildDists(self): 505 | for distname in self.dists.__dict__: 506 | self.dists.__dict__[distname].category = self.category; 507 | self.dists.__dict__[distname].ldesc = self.longDesc; 508 | self.dists.__dict__[distname].sdesc = self.shortDesc; 509 | self.dists.__dict__[distname].requires = self.requires; 510 | 511 | for distname in self.dists.__dict__: 512 | self.dists.__dict__[distname].version = self.version.__dict__[distname]; 513 | for distname in self.dists.__dict__: 514 | self.dists.__dict__[distname].install = self.install.__dict__[distname].toString(); 515 | for distname in self.dists.__dict__: 516 | self.dists.__dict__[distname].source = self.source.__dict__[distname].toString(); 517 | 518 | def _buildPkg(self): 519 | for distname in self.dists.__dict__: 520 | self._buildDistFiles(distname); 521 | 522 | def _buildDistFiles(self, distname='curr'): 523 | # create build directory 524 | if not self._generated : 525 | self._generateDistFiles(distname); 526 | 527 | tar_name = os.path.join(self._localMirror, self.install.__dict__[distname].url); 528 | tar_src_name = os.path.join(self._localMirror, self.source.__dict__[distname].url); 529 | 530 | digest = self._hashFile(tar_name, self._hashAlgo); 531 | digest_src = self._hashFile(tar_src_name, self._hashAlgo); 532 | 533 | self.install.__dict__[distname].size = self._fileSize(tar_name); 534 | self.source.__dict__[distname].size = self._fileSize(tar_src_name); 535 | self.install.__dict__[distname].digest = digest; 536 | self.source.__dict__[distname].digest = digest_src; 537 | 538 | self.filelist = self._getFileList(distname); 539 | 540 | def _generateDistFiles(self, distname='curr'): 541 | dirname = os.path.join(self._tmpdir, self.name + self.version.__dict__[distname]); 542 | os.makedirs(dirname); 543 | usr_d = os.path.join(dirname, "usr"); 544 | etc_d = os.path.join(dirname, "etc"); 545 | var_d = os.path.join(dirname, "var"); 546 | bin_d = os.path.join(dirname, "usr", "bin"); 547 | share_d = os.path.join(dirname, "usr", "share", self.name); 548 | version_d = os.path.join(share_d, self.version.__dict__[distname]); 549 | postinstall_d = os.path.join(dirname, "etc", "postinstall"); 550 | postremove_d = os.path.join(dirname, "etc", "postremove"); 551 | preremove_d = os.path.join(dirname, "etc", "preremove"); 552 | marker_d = os.path.join(dirname, "var", self.name); 553 | os.makedirs(bin_d); 554 | os.makedirs(postinstall_d); 555 | os.makedirs(postremove_d); 556 | os.makedirs(preremove_d); 557 | os.makedirs(marker_d); 558 | os.makedirs(share_d); 559 | os.makedirs(version_d); 560 | bin_f = os.path.join(bin_d, self.name); 561 | link_bin_f = os.path.join(bin_d, self.name + "-link"); 562 | link_version_d = os.path.join(share_d, "current"); 563 | hardlink_bin_f = os.path.join(bin_d, self.name + "-hardlink"); 564 | postinstall_f = os.path.join(postinstall_d, self.name + self._scriptsExt); 565 | postremove_f = os.path.join(postremove_d, self.name + self._scriptsExt); 566 | preremove_f = os.path.join(preremove_d, self.name + self._scriptsExt); 567 | marker_f = os.path.join(marker_d, "version"); 568 | 569 | # create exec "#!/usr/bin/sh\necho running;" > root/usr/bin 570 | # link 571 | # hard link 572 | f = open(bin_f, 'w'); 573 | f.write('#!/bin/sh\necho "running";'); 574 | f.close(); 575 | Process(['ln', '-s', self.name, link_bin_f]).mustRun(); 576 | Process(['ln', bin_f, hardlink_bin_f]).mustRun(); 577 | Process([ 578 | 'ln', 579 | '-s', 580 | os.path.relpath(version_d, os.path.dirname(link_version_d)), 581 | link_version_d, 582 | ]).mustRun(); 583 | 584 | self._writeScript(postinstall_f, 0); 585 | self._writeScript(preremove_f, 0); 586 | self._writeScript(postremove_f, 0); 587 | 588 | # create version marker > root/var// 589 | f = open(marker_f, 'w'); 590 | f.write(self.version.__dict__[distname]); 591 | f.close(); 592 | # build install tar 593 | tar_name = os.path.join( 594 | self._localMirror, 595 | self.install.__dict__[distname].url 596 | ); 597 | tarInstallPath = ".".join(tar_name.split(".")[:-1]); 598 | tar = tarfile.open(tarInstallPath, mode='w'); 599 | for name in [usr_d, etc_d, var_d]: 600 | tar.add(name, os.path.basename(name)); 601 | members = tar.getmembers(); 602 | tar.close(); 603 | self._compressFollowingTargetExtension(tarInstallPath, tar_name); 604 | del tarInstallPath; 605 | 606 | # Force slash to the end of each directories 607 | lst = []; 608 | for m in members: 609 | if m.isdir() and not m.name.endswith("/"): 610 | lst.append(m.name + "/"); 611 | else: 612 | lst.append(m.name); 613 | f = open("{0}.lst".format(tar_name), 'w'); 614 | f.write("\n".join(lst)); 615 | f.close(); 616 | 617 | # build source tar 618 | tar_src_name = os.path.join( 619 | self._localMirror, 620 | self.source.__dict__[distname].url 621 | ); 622 | tarSrcPath = ".".join(tar_src_name.split(".")[:-1]); 623 | tar = tarfile.open(tarSrcPath, mode='w'); 624 | tar.add(dirname, "{0}-{1}".format( 625 | self.name,self.version.__dict__[distname] 626 | )); 627 | tar.close(); 628 | self._compressFollowingTargetExtension(tarSrcPath, tar_src_name); 629 | del tarSrcPath; 630 | 631 | md5sum = self._md5Sum(tar_name); 632 | md5sum_src = self._md5Sum(tar_src_name); 633 | 634 | md5_sum_f = os.path.join(os.path.dirname(tar_name), "md5.sum"); 635 | 636 | f = open(md5_sum_f, 'a'); 637 | f.write( 638 | "{0} {1}{LF}" 639 | "{2} {3}{LF}" 640 | "".format( 641 | md5sum, 642 | os.path.basename(self.install.__dict__[distname].url), 643 | md5sum_src, 644 | os.path.basename(self.source.__dict__[distname].url), 645 | LF="\n" 646 | )); 647 | f.close(); 648 | 649 | def _compressFollowingTargetExtension(self, srcPath, targetPath): 650 | compression = targetPath.split(".")[-1]; 651 | if "bz2" == compression : 652 | f = open(srcPath, 'rb'); 653 | contents = f.read(); 654 | compressed = bz2.compress(contents); 655 | f.close(); 656 | f = open(targetPath, 'wb'); 657 | f.write(compressed); 658 | f.close(); 659 | os.remove(srcPath); 660 | elif "xz" == compression : 661 | Process(['xz', '-f', srcPath]).mustRun(); 662 | 663 | def _md5Sum(self, path): 664 | """Generate the md5sum from a file. 665 | 666 | @param path: str The path to a file for unsed to generate md5sum. 667 | 668 | @return: str The resulted md5sum. 669 | """ 670 | return self._hashFile(path, 'md5'); 671 | 672 | def _hashFile(self, path, algorithm): 673 | """Generate a hash value using the contents of a given file. 674 | 675 | Supported algorithm: 676 | * md5 677 | * sha256 678 | * sha512 679 | 680 | @param path: str The path to a file for used to generate the digest. 681 | @param algorithm: str The name of selected hashing algorithm. 682 | 683 | @return: str The resulted digest. 684 | """ 685 | f = open(path, 'rb'); 686 | content = f.read(); 687 | f.close(); 688 | 689 | algorithms = [ 690 | 'md5', 691 | 'sha256', 692 | 'sha512', 693 | ]; 694 | 695 | if algorithm in algorithms : 696 | return getattr(hashlib, algorithm)(content).hexdigest(); 697 | 698 | def _fileSize(self, path): 699 | """Determine the file size for the specified path. 700 | 701 | @param path: str The path to check the file size. 702 | 703 | @return: integer The file size. 704 | """ 705 | return long(os.path.getsize(path)); 706 | 707 | def _getFileList(self, distname): 708 | """Gets file list from a distribution. 709 | 710 | @param distname: str The distribution name. 711 | 712 | @return: list A list of paths that contains the given distribution. 713 | """ 714 | tar_name = os.path.join(self._localMirror, self.install.__dict__[distname].url); 715 | 716 | f = open("{0}.lst".format(tar_name), 'r'); 717 | contents = f.read(); 718 | f.close(); 719 | 720 | lst = contents.split("\n"); 721 | 722 | return lst; 723 | 724 | class DistNameStruct(): 725 | def __init__(self): 726 | self.curr = None; 727 | self.prev = None; 728 | self.test = None; 729 | 730 | def __getitem__(self, key): 731 | return self.__dict__[key]; 732 | 733 | class DistStruct(): 734 | def __init__(self): 735 | self.category = None; 736 | self.sdesc = None; 737 | self.ldesc = None; 738 | self.requires = None; 739 | self.version = None; 740 | self.install = None; 741 | self.source = None; 742 | 743 | class DistsStruct(DistNameStruct): 744 | def __init__(self): 745 | self.curr = DistStruct(); 746 | self.prev = DistStruct(); 747 | self.test = DistStruct(); 748 | 749 | class FileStruct(): 750 | def __init__(self): 751 | self.url = ""; 752 | self.size = "1024"; 753 | self.digest = "md5"; 754 | 755 | def __str__(self): 756 | return self.toString(); 757 | 758 | def __repr__(self): 759 | return self.toString(); 760 | 761 | def toString(self): 762 | ball = "{0} {1} {2}".format( 763 | self.url, 764 | self.size, 765 | self.digest 766 | ); 767 | return ball; 768 | -------------------------------------------------------------------------------- /src/cygapt/url_opener.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import print_function; 15 | from __future__ import absolute_import; 16 | 17 | import sys; 18 | import urllib; 19 | 20 | class CygAptURLopener(urllib.FancyURLopener): 21 | BAR_MAX = 40; 22 | 23 | def __init__(self, verbose, *args): 24 | urllib.FancyURLopener.__init__(self, *args); 25 | self.__verbose = verbose; 26 | self.__errorCode = 200; 27 | 28 | def getErrorCode(self): 29 | return self.__errorCode; 30 | 31 | def setErrorCode(self, code): 32 | self.__errorCode = int(code); 33 | 34 | def http_error_default(self, url, fp, errcode, errmsg, headers): 35 | self.__errorCode = errcode; 36 | return urllib.FancyURLopener.http_error_default( 37 | self, 38 | url, 39 | fp, 40 | errcode, 41 | errmsg, 42 | headers 43 | ); 44 | 45 | def dlProgress(self, count, blockSize, totalSize): 46 | if self.__errorCode != 200: 47 | return; 48 | if not self.__verbose: 49 | return; 50 | barmax = self.BAR_MAX; 51 | ratio = min((count * blockSize), totalSize) / float(totalSize); 52 | bar = int(barmax * ratio); 53 | if ratio == 1.0: 54 | sys.stdout.write(" "*70 + "\r"); 55 | sys.stdout.flush(); 56 | else: 57 | print("[", end=""); 58 | for i in range(barmax): 59 | if i < bar: 60 | sys.stdout.write("="); 61 | elif i == bar: 62 | sys.stdout.write(">"); 63 | else: 64 | sys.stdout.write(" "); 65 | sys.stdout.write("]\r"); 66 | sys.stdout.flush(); 67 | -------------------------------------------------------------------------------- /src/cygapt/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | 14 | from __future__ import print_function; 15 | from __future__ import absolute_import; 16 | 17 | import os; 18 | import re; 19 | import shutil; 20 | import struct; 21 | import sys; 22 | import tarfile; 23 | import urlparse; 24 | import stat; 25 | import warnings; 26 | 27 | from cygapt.exception import ApplicationException; 28 | from cygapt.exception import InvalidArgumentException; 29 | from cygapt.exception import UnexpectedValueException; 30 | from cygapt.url_opener import CygAptURLopener; 31 | from cygapt.structure import ConfigStructure; 32 | from cygapt.process import Process; 33 | 34 | def cygpath(path): 35 | p = Process(["cygpath", path]); 36 | p.run(); 37 | dospath = p.getOutput().strip(); 38 | return dospath; 39 | 40 | def parse_rc(cyg_apt_rc): 41 | """Parse the user configuration file. 42 | 43 | @param cyg_apt_rc: str The path to the user configuration. 44 | 45 | @return: ConfigStructure The configuration. 46 | """ 47 | f = open(cyg_apt_rc); 48 | lines = f.readlines(); 49 | f.close(); 50 | rc_regex = re.compile(r"^\s*(\w+)\s*=\s*(.*)\s*$"); 51 | always_update = False; 52 | config = ConfigStructure(); 53 | for i in lines: 54 | result = rc_regex.search(i); 55 | if result: 56 | k = result.group(1); 57 | v = result.group(2); 58 | if k in config.__dict__ : 59 | config.__dict__[k] = str(v).rstrip().strip('\'"'); 60 | if 'setup_ini' == k : 61 | warnings.warn( 62 | "The configuration field `setup_ini` is deprecated" 63 | " since version 1.1 and will be removed in 2.0.", 64 | DeprecationWarning, 65 | ); 66 | 67 | if config.always_update in [True, 'True', 'true', 'Yes', 'yes']: 68 | always_update = True; 69 | else: 70 | always_update = False; 71 | 72 | config.always_update = always_update; 73 | 74 | return config; 75 | 76 | def remove_if_exists(fn): 77 | try: 78 | os.remove(fn); 79 | except OSError: 80 | pass; 81 | 82 | def open_tarfile(ball, xzPath='xz'): 83 | """Opens a tar file like `tarfile.open`. 84 | 85 | Supports also LZMA compressed tarballs. 86 | 87 | @param ball: str A tar file, it can be compressed 88 | @param xzPath: str A path to the lzma program 89 | 90 | @return: TarFile An appropriate TarFile instance 91 | """ 92 | assert isinstance(xzPath, str); 93 | 94 | ball_orig = ball; 95 | if ball.lower().endswith('.tar.xz'): 96 | ball_orig = ball; 97 | ball = ball[:-3]; # remove .xz extension 98 | remove_if_exists(ball); 99 | Process([xzPath, '-k', '-d', ball_orig]).mustRun(); 100 | tf = tarfile.open(ball); 101 | if ball_orig != ball: 102 | tf_close_orig = tf.close; 103 | def tf_close(): 104 | retValue = tf_close_orig(); 105 | remove_if_exists(ball); 106 | return retValue; 107 | tf.close = tf_close; 108 | return tf; 109 | 110 | def is_tarfile(ball): 111 | return ball.lower().endswith('.tar.xz') or tarfile.is_tarfile(ball); 112 | 113 | def prsort(lst): 114 | lst.sort(); 115 | lst.reverse(); 116 | return lst; 117 | 118 | def rename(src, dest): 119 | if os.path.exists(dest): 120 | os.remove(dest); 121 | os.rename(src, dest); 122 | 123 | def rmtree(path): 124 | """Removes the given path without following symlinks. 125 | 126 | It can remove directory content also if it does not have permissions. 127 | 128 | @param path: str A link, file or directory path. 129 | 130 | @raise OSError: When the path cannot be removed. 131 | """ 132 | rmtree_helper(path); 133 | 134 | if os.path.islink(path) or os.path.isfile(path) : 135 | os.remove(path); 136 | 137 | return; 138 | 139 | if os.path.isdir(path) : 140 | shutil.rmtree(path); 141 | 142 | def rmtree_helper(path): 143 | """Adds reading and writing permissions for owner for each path recursively. 144 | 145 | @param path: str The path to adding permissions. 146 | 147 | @raise OSError: When permissions cannot be set. 148 | """ 149 | if os.path.islink(path) : 150 | return; 151 | 152 | if os.path.exists(path) : 153 | # Adds reading and writing permissions for owner. 154 | os.chmod(path, stat.S_IWUSR | stat.S_IRUSR | os.stat(path)[stat.ST_MODE]); 155 | 156 | if os.path.isdir(path) : 157 | files = os.listdir(path); 158 | for x in files: 159 | fullpath = os.path.join(path, x); 160 | rmtree_helper(fullpath); 161 | 162 | def uri_get(directory, uri, verbose=False): 163 | up = urlparse.urlparse(uri); 164 | scriptname = os.path.basename(sys.argv[0]); 165 | 166 | if up.scheme == "file": 167 | shutil.copy(uri[7:], directory); 168 | if verbose: 169 | print("cp {0} {1}".format(uri[7:], directory)); 170 | elif up.scheme == "http" or up.scheme == "ftp": 171 | url_base = os.path.basename(up.path); 172 | old_cwd = os.getcwd(); 173 | os.chdir(directory); 174 | if verbose: 175 | print("\r{0}: downloading: {1}".format(scriptname, uri)); 176 | try: 177 | opener = CygAptURLopener(verbose); 178 | opener.retrieve( 179 | uri, 180 | "{0}.tmp".format(url_base), 181 | reporthook=opener.dlProgress 182 | ); 183 | except IOError: 184 | opener.setErrorCode(1); 185 | finally: 186 | opener.close(); 187 | 188 | if opener.getErrorCode() == 200: 189 | rename(url_base + ".tmp", url_base); 190 | else: 191 | if os.path.exists(url_base + ".tmp"): 192 | os.remove(url_base + ".tmp"); 193 | os.chdir(old_cwd); 194 | raise RequestException( 195 | "{0} unreached URL {1}" 196 | "".format(opener.getErrorCode(), uri) 197 | ); 198 | os.chdir(old_cwd); 199 | else: 200 | raise InvalidArgumentException("bad URL {0}".format(uri)); 201 | 202 | def pe_is_64_bit(fn): 203 | """Reads the header of a PE (.exe or .dll) file to determine its 204 | architecture. 205 | 206 | @param fn: str The PE filename to read. 207 | 208 | @return: bool Whether the PE file is 64-bit. 209 | 210 | @raise UnexpectedValueException: If an invalid value is found. 211 | @raise IOError: If a read error occurs. 212 | """ 213 | with open(fn, "rb") as f: 214 | if "MZ" != f.read(2) : 215 | raise UnexpectedValueException( 216 | "File '{0}' is not a DOS executable." 217 | "".format(fn) 218 | ); 219 | f.seek(0x3c); # Offset of PE header (e_lfanew in DOS header) 220 | pe_header = struct.unpack(' 5 | sub 1024g/A1DB7B5C 2008-06-13 6 | 7 | -----BEGIN PGP PUBLIC KEY BLOCK----- 8 | Version: GnuPG v1.4.9 (Cygwin) 9 | 10 | mQGiBEhS+m8RBAC5bn3n2yG0eqNlpg/D7DkZXQfFUBZN1D4sL/NsXKISQkA3FsiT 11 | enDYDMFCy3NJiCDcihJprP2xs4Fc25MEcmJ4j9X93bCV4DtHv22qO1XWGkxr/XQJ 12 | ZxYmUxFhezBOCZd+wXir0izIsGghR1+ei6i+vL4mRYy8wpMCKwf8X0qRywCg1l2J 13 | a91PsTO6itVUACYMvKNFCHED/RenUG+kYRch9YHuDwG9LxkhgwSEZ0NIGUgZLHMY 14 | HZDlcWBRoV6uPcqa2iKs8vvAENMcGWqo+fuRycGQ6+zlFn29IoHrcxMMM27VpifQ 15 | 91N5AqgSMPOIFkKse2VNFQ2jL4t1NfdQazRvZojwkXuYY9kB16h0Y2Zme1Pt5RgC 16 | /wLhA/4lkttrs3ElzkAOZtrTwi7tCJnNR8/5VYnVd63NEGyAXk/qralUoQO+GNQf 17 | ZXJUvCoYIhinHh7vzfqMT2l1gGi0FuSULX3dY5jsm0Vcu+f7XLlDoEurx1vDYCv+ 18 | 9QABQDDPXuZJk55pDG1TQbvAFV8U6wWdCI5hBwcJsDfwLMzxN7QaQ3lnd2luIDxj 19 | eWd3aW5AY3lnd2luLmNvbT6IXgQTEQIAHgUCSFL6bwIbAwYLCQgHAwIDFQIDAxYC 20 | AQIeAQIXgAAKCRCpomL/Z2BBuncZAKCmfQS2ROcl9H8VaKmdMOB/loNRLwCfTqxf 21 | W6L6ifl1uDwoH8t83PRjkRWIRgQQEQIABgUCSFL+qAAKCRBN0oLlajiMPmH+AKDB 22 | vgDIxkX4PKEYOkXrwPgcKGdHowCg6tsG2Bqj3cSkoISe7f3J5v87f0+5AQ0ESFL6 23 | cBAEAIqcw0vcqdTvuukm6oiRUxkQ/jrP+4w2FNKEK1sYG5+cbwVrf3ISTUrbTRbV 24 | 3Fz5npefwaLNlIUjVYCBBWL4PuUtL4cCrmbvMXabSYfz2qg/aqqw9xNa4G9GCdF4 25 | j9AIZaV86UHElC1wZAHTvMEdgHs8ek9kb5rDDChUgyE+nXQ7AAMFA/4rXq6swR8m 26 | /1O8nRgNkwDvas3DbUOIdoYoFPrN7e2LBuYWFDB+O2IUn6tAgHhDxpzO9vw58U5a 27 | /z1zm63Lf9ybHDV4c3Rqie2u2oberj1KKStnn27KlGGvFY9kWe9WKh+ZN90/oqVG 28 | BT4+obmTiwUmVJIUy4vSZDjC0VqZHLxdOIhJBBgRAgAJBQJIUvpwAhsMAAoJEKmi 29 | Yv9nYEG6euAAniloWCmYSp4ULCHauEMbopO2jFlwAKCwlu0FsfcO/2+AresM67hC 30 | SwxQ+g== 31 | =XD1x 32 | -----END PGP PUBLIC KEY BLOCK----- 33 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | from cygapt.main import CygAptMain; 18 | 19 | if __name__ == "__main__": 20 | CygAptMain(); 21 | -------------------------------------------------------------------------------- /src/postinstall-gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$ID_PREFIX" ]; 4 | then 5 | ID_PREFIX="usr"; 6 | fi; 7 | 8 | if [ -z "$ID_EXEC" ]; 9 | then 10 | ID_EXEC="$ID_PREFIX/bin"; 11 | fi; 12 | 13 | if [ -z "$ID_DATA" ]; 14 | then 15 | ID_DATA="$ID_PREFIX/share"; 16 | fi; 17 | 18 | if [ -z "$EXENAME" ]; 19 | then 20 | EXENAME="cyg-apt"; 21 | fi; 22 | 23 | if [ -z "$GPG_CYGWIN_PUBKEY" ]; 24 | then 25 | GPG_CYGWIN_PUBKEY="cygwin.sig"; 26 | fi; 27 | 28 | echo "#!/bin/bash 29 | # Add Cygwin's public key to the gpg keyring 30 | /usr/bin/gpg --import --no-secmem-warning \"$ID_ROOT/$ID_DATA/$EXENAME/$GPG_CYGWIN_PUBKEY\" 31 | 32 | # Initialize $EXEC 33 | \"$ID_ROOT/$ID_EXEC/$EXENAME\" setup 34 | "; 35 | 36 | exit 0; 37 | -------------------------------------------------------------------------------- /src/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | ######################## BEGIN LICENSE BLOCK ######################## 4 | # This file is part of the cygapt package. 5 | # 6 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 7 | # 2002-2009 Chris Cormie 8 | # 2012 James Nylen 9 | # 2012-2014 Alexandre Quercia 10 | # 11 | # For the full copyright and license information, please view the 12 | # LICENSE file that was distributed with this source code. 13 | ######################### END LICENSE BLOCK ######################### 14 | 15 | from __future__ import absolute_import; 16 | 17 | from distutils.core import setup; 18 | import os; 19 | 20 | realpathfile = os.path.realpath(os.path.dirname(__file__)); 21 | realpathcwd = os.path.realpath(os.getcwd()); 22 | 23 | if realpathfile != realpathcwd: 24 | os.chdir(realpathfile); 25 | 26 | try: 27 | version = os.environ['VERSION']; 28 | except KeyError: 29 | version = "1.1.0rc1"; 30 | 31 | try: 32 | pkgname = os.environ['PYPKG']; 33 | except KeyError: 34 | pkgname = "cygapt"; 35 | 36 | f = open("../README.md"); 37 | long_description = f.read(); 38 | f.close(); 39 | 40 | setup( 41 | name=pkgname, 42 | packages=[ 43 | 'cygapt', 44 | 'cygapt.process', 45 | 'cygapt.test', 46 | 'cygapt.test.case', 47 | 'cygapt.test.case.py2', 48 | 'cygapt.test.case.py2.minor6', 49 | ], 50 | package_data={pkgname: [ 51 | 'LICENSE', 52 | 'test/fixtures/utils/*', 53 | ]}, 54 | version=version, 55 | description="A Cygwin command line package management tool.", 56 | long_description=long_description, 57 | license="GPL-3.0", 58 | url="https://github.com/nylen/cyg-apt", 59 | author="Jan Nieuwenhuizen, Chris Cormie, James Nylen, Alexandre Quercia", 60 | author_email="cjcormie@gmail.com, janneke@gnu.org, jnylen@gmail.com, alquerci@email.com", 61 | maintainer="Alexandre Quercia", 62 | maintainer_email="alquerci@email.com", 63 | platforms="cygwin", 64 | classifiers=[ 65 | 'Development Status :: 5 - Production/Stable', 66 | 'Environment :: Console', 67 | 'Intended Audience :: System Administrators', 68 | 'License :: OSI Approved :: GNU General Public License, Version 3', 69 | 'Operating System :: Microsoft :: Windows', 70 | 'Programming Language :: Python', 71 | ], 72 | ); 73 | 74 | os.chdir(realpathcwd); 75 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | all:: sourcetest 2 | 3 | TEST_SCRIPT = -m cygapt.test.__main__ 4 | ARGUMENTS = 5 | ARGUMENTS += -v 6 | 7 | # Print a warning each time it occurs, used for deprecation tests 8 | # see https://docs.python.org/2/using/cmdline.html#cmdoption-W 9 | PYTHON += -Wall 10 | 11 | sourcetest: FORCE 12 | cd $(SD_SRC); $(PYTHON) $(TEST_SCRIPT) $(ARGUMENTS) 13 | 14 | installtest: FORCE 15 | @$(PYTHON) $(TEST_SCRIPT) $(ARGUMENTS) 16 | 17 | clean: FORCE 18 | 19 | 20 | .PHONY: FORCE 21 | .EXPORT_ALL_VARIABLES: 22 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | SRC = completion.bash 2 | D_COMPLETION = bash_completion.d 3 | D_BUILD = $(SD_BUILD)/$(ID_SYSCONF)/$(D_COMPLETION) 4 | EXEC = $(EXENAME) 5 | 6 | all:: $(D_BUILD)/$(EXEC) 7 | 8 | $(D_BUILD): 9 | $(MKDIR) $(D_BUILD) 10 | 11 | $(D_BUILD)/$(EXEC): $(SRC) $(D_BUILD) 12 | $(CP) "$<" "$@" 13 | 14 | install: $(D_BUILD)/$(EXEC) 15 | $(INSTALL) -d -m 755 $(ID_ROOT)/$(ID_SYSCONF)/$(D_COMPLETION) 16 | $(INSTALL) -m 755 "$<" $(ID_ROOT)/$(ID_SYSCONF)/$(D_COMPLETION) 17 | 18 | clean: FORCE 19 | 20 | 21 | .PHONY: FORCE 22 | .EXPORT_ALL_VARIABLES: 23 | -------------------------------------------------------------------------------- /tools/completion.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ######################## BEGIN LICENSE BLOCK ######################## 3 | # This file is part of the cygapt package. 4 | # 5 | # Copyright (C) 2002-2009 Jan Nieuwenhuizen 6 | # 2002-2009 Chris Cormie 7 | # 2012 James Nylen 8 | # 2012-2014 Alexandre Quercia 9 | # 10 | # For the full copyright and license information, please view the 11 | # LICENSE file that was distributed with this source code. 12 | ######################### END LICENSE BLOCK ######################### 13 | # 14 | # bash completion support for cyg-apt. 15 | # 16 | 17 | 18 | ## 19 | # Sets all available packages in $_cyg_apt_all_packages 20 | ## 21 | _cyg_apt_set_all_packages() 22 | { 23 | if [ -z "${_cyg_apt_all_packages}" ] ; then 24 | _cyg_apt_all_packages="$("${COMP_WORDS[0]}" search . -s | cut -d " " -f 1)"; 25 | fi; 26 | return 0; 27 | } 28 | 29 | _cyg_apt() 30 | { 31 | local cur prev cmds long_opts dist_opts mirrors; 32 | COMPREPLY=(); 33 | cur="${COMP_WORDS[COMP_CWORD]}"; 34 | prev="${COMP_WORDS[COMP_CWORD-1]}"; 35 | cmds=" 36 | setup 37 | update 38 | ball 39 | download 40 | filelist 41 | find 42 | help 43 | install 44 | list 45 | checksum 46 | missing 47 | new 48 | purge 49 | remove 50 | requires 51 | search 52 | show 53 | source 54 | upgrade 55 | url 56 | version 57 | "; 58 | long_opts=" 59 | --quiet 60 | --download 61 | --mirror 62 | --dist 63 | --no-deps 64 | --regexp 65 | --force 66 | --no-verify 67 | --nopostinstall 68 | --nopostremove 69 | --help 70 | "; 71 | dist_opts=" 72 | curr 73 | test 74 | prev 75 | "; 76 | mirrors=" 77 | ftp://ftp.is.co.za/mirrors/cygwin/ 78 | http://mirrors.163.com/cygwin/ 79 | ftp://ftp.iitm.ac.in/cygwin/ 80 | http://mirror.internode.on.net/pub/cygwin/ 81 | ftp://mirror.csclub.uwaterloo.ca/cygwin/ 82 | http://cygwin.mirror.rafal.ca/ 83 | http://cygwin.uib.no/ 84 | ftp://ftp.heanet.ie/pub/cygwin/ 85 | http://cygwin.osuosl.org/ 86 | ftp://lug.mtu.edu/cygwin/ 87 | "; 88 | 89 | case "${COMP_CWORD}" in 90 | 1 ) 91 | COMPREPLY=( $(compgen -W "${cmds}" -- "${cur}") ); 92 | return 0; 93 | ;; 94 | * ) 95 | case "$prev" in 96 | --dist|-t ) 97 | # list available distribution 98 | COMPREPLY=( $(compgen -W "${dist_opts}" -- "${cur}") ); 99 | return 0; 100 | ;; 101 | --mirror|-m ) 102 | # list some mirrors 103 | COMPREPLY=( $(compgen -W "${mirrors}" -- "${cur}") ); 104 | return 0; 105 | ;; 106 | * ) 107 | ;; 108 | esac; 109 | 110 | if [[ "$cur" == -* ]] ; then 111 | COMPREPLY=( $(compgen -W "${long_opts}" -- "${cur}") ); 112 | return 0; 113 | fi 114 | 115 | case "${COMP_WORDS[1]}" in 116 | ball|requires|show|download|checksum|purge|search|source|url|filelist|install|missing|remove|version ) 117 | _cyg_apt_set_all_packages; 118 | COMPREPLY=( $(compgen -W "${_cyg_apt_all_packages}" -- "${cur}") ); 119 | return 0; 120 | ;; 121 | * ) 122 | ;; 123 | esac; 124 | ;; 125 | esac; 126 | 127 | return 0; 128 | } 129 | 130 | complete -F _cyg_apt cyg-apt; 131 | --------------------------------------------------------------------------------