├── Setup.lhs ├── .gitignore ├── README.md ├── appveyor.yml ├── CHANGELOG.md ├── release ├── temporary.cabal ├── LICENSE ├── stack.yaml ├── tests └── test.hs ├── .travis.yml └── System └── IO └── Temp.hs /Setup.lhs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env runhaskell 2 | > import Distribution.Simple 3 | > main = defaultMain -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS junk 2 | Thumbs.db 3 | .DS_Store 4 | 5 | # Build artifacts 6 | dist/ 7 | dist-newstyle/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | temporary 2 | ========= 3 | 4 | Portable temporary file and directory support for Windows and Unix. 5 | 6 | Hackage: [temporary](http://hackage.haskell.org/package/temporary) 7 | 8 | Maintained by [Mateusz Kowalczyk](https://github.com/Fuuzetsu) and 9 | [Roman Cheplyaka](https://github.com/feuerbach). 10 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # copied from the filepath package 2 | cache: 3 | - "c:\\sr -> appveyor.yml" # stack root, short paths == less problems 4 | 5 | build: off 6 | 7 | before_test: 8 | - set PATH=C:\Program Files\Git\mingw64\bin;%PATH% # for curl 9 | - curl -ostack.zip -L --insecure http://www.stackage.org/stack/windows-i386 10 | - 7z x stack.zip stack.exe 11 | 12 | clone_folder: "c:\\project" 13 | environment: 14 | global: 15 | STACK_ROOT: "c:\\sr" 16 | 17 | test_script: 18 | - stack setup > nul 19 | - echo "" | stack --no-terminal test 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | * Add new function: `createTempFileName`. 4 | 5 | ## 1.3 6 | 7 | * Generated directory names are now based on random hex strings rather than PIDs. 8 | 9 | This got a major version bump as a courtesy to users who may depend on the 10 | specific form of generated names, but that form is not part of the API 11 | contract and should not be depended upon. 12 | 13 | ## 1.2.1.1 14 | 15 | * Improve the docs 16 | 17 | ## 1.2.1 18 | 19 | * Limit support to GHC 7.0+ 20 | * Add new functions: `writeTempFile,` `writeSystemTempFile,` `emptyTempFile,` `emptySystemTempFile` 21 | * Make sure that system* functions return canonicalized paths 22 | * Modernize the code base, add tests and documentation 23 | 24 | ## 1.2.0.4 25 | 26 | * Update maintainership information 27 | * Fix the docs 28 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | echo "Have you updated the version number? Type 'yes' if you have!" 5 | read version_response 6 | 7 | if [ "$version_response" != "yes" ]; then 8 | echo "Go and update the version number" 9 | exit 1 10 | fi 11 | 12 | sdist_output=`runghc Setup.lhs sdist` 13 | 14 | if [ "$?" != "0" ]; then 15 | echo "Cabal sdist failed, aborting" 16 | exit 1 17 | fi 18 | 19 | # Want to find a line like: 20 | # Source tarball created: dist/ansi-terminal-0.1.tar.gz 21 | 22 | # Test this with: 23 | # runghc Setup.lhs sdist | grep ... 24 | filename=`echo $sdist_output | sed 's/.*Source tarball created: \([^ ]*\).*/\1/'` 25 | echo "Filename: $filename" 26 | 27 | if [ "$filename" = "$sdist_output" ]; then 28 | echo "Could not find filename, aborting" 29 | exit 1 30 | fi 31 | 32 | # Test this with: 33 | # echo dist/ansi-terminal-0.1.tar.gz | sed ... 34 | version=`echo $filename | sed 's/^[^0-9]*\([0-9\.]*\).tar.gz$/\1/'` 35 | echo "Version: $version" 36 | 37 | if [ "$version" = "$filename" ]; then 38 | echo "Could not find version, aborting" 39 | exit 1 40 | fi 41 | 42 | echo "This is your last chance to abort! I'm going to upload in 10 seconds" 43 | sleep 10 44 | 45 | git tag "v$version" 46 | 47 | if [ "$?" != "0" ]; then 48 | echo "Git tag failed, aborting" 49 | exit 1 50 | fi 51 | 52 | # You need to have stored your Hackage username and password as directed by cabal-upload 53 | # I use -v3 because otherwise the error messages can be cryptic :-) 54 | cabal upload -v3 $filename 55 | 56 | if [ "$?" != "0" ]; then 57 | echo "Hackage upload failed, aborting" 58 | exit 1 59 | fi 60 | 61 | # Success! 62 | exit 0 -------------------------------------------------------------------------------- /temporary.cabal: -------------------------------------------------------------------------------- 1 | name: temporary 2 | version: 1.3 3 | cabal-version: >= 1.10 4 | synopsis: Portable temporary file and directory support 5 | description: Functions for creating temporary files and directories. 6 | category: System, Utils 7 | license: BSD3 8 | license-file: LICENSE 9 | maintainer: Mateusz Kowalczyk , Roman Cheplyaka 10 | homepage: https://github.com/feuerbach/temporary 11 | build-type: Simple 12 | extra-source-files: CHANGELOG.md 13 | 14 | source-repository head 15 | type: git 16 | location: git://github.com/feuerbach/temporary.git 17 | 18 | Library 19 | default-language: 20 | Haskell2010 21 | exposed-modules: System.IO.Temp 22 | build-depends: base >= 3 && < 10, filepath >= 1.1, directory >= 1.0, 23 | transformers >= 0.2.0.0, exceptions >= 0.6, random >= 1.1 24 | -- note: the transformers dependency is needed for MonadIO 25 | -- on older GHCs; on newer ones, it is included in base. 26 | ghc-options: -Wall 27 | 28 | if !os(windows) 29 | build-depends: unix >= 2.3 30 | 31 | test-suite test 32 | default-language: 33 | Haskell2010 34 | type: 35 | exitcode-stdio-1.0 36 | hs-source-dirs: 37 | tests 38 | main-is: 39 | test.hs 40 | ghc-options: -threaded -with-rtsopts=-N2 41 | build-depends: 42 | base >= 4.3 && < 5 43 | , directory 44 | , tasty 45 | , tasty-hunit 46 | , temporary 47 | , filepath 48 | , base-compat 49 | if !os(windows) 50 | build-depends: unix >= 2.3 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2 | (c) 2003-2006, Isaac Jones 3 | (c) 2005-2009, Duncan Coutts 4 | (c) 2008, Maximilian Bolingbroke 5 | ... and other contributors 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted 10 | provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this list of 13 | conditions and the following disclaimer. 14 | * Redistributions in binary form must reproduce the above copyright notice, this list of 15 | conditions and the following disclaimer in the documentation and/or other materials 16 | provided with the distribution. 17 | * Neither the name of Maximilian Bolingbroke nor the names of other contributors may be used to 18 | endorse or promote products derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 21 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 22 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 26 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 27 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-10.5 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.3" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /tests/test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | import Test.Tasty 3 | import Test.Tasty.HUnit 4 | 5 | import Control.Monad 6 | import Control.Concurrent 7 | import Control.Concurrent.MVar 8 | import Control.Exception 9 | import System.Directory 10 | import System.IO 11 | import System.FilePath 12 | import System.Environment.Compat 13 | import Data.Bits 14 | import Data.List 15 | import GHC.IO.Handle 16 | #ifndef mingw32_HOST_OS 17 | import System.Posix.Files 18 | #endif 19 | 20 | import System.IO.Temp 21 | 22 | main = do 23 | -- force single-thread execution, because changing TMPDIR in one of the 24 | -- tests may leak to the other tests 25 | setEnv "TASTY_NUM_THREADS" "1" 26 | #ifndef mingw32_HOST_OS 27 | setFileCreationMask 0 28 | #endif 29 | sys_tmp_dir <- getCanonicalTemporaryDirectory 30 | 31 | defaultMain $ testGroup "Tests" 32 | [ testCase "openNewBinaryFile" $ do 33 | (fp, fh) <- openNewBinaryFile sys_tmp_dir "test.txt" 34 | let fn = takeFileName fp 35 | assertBool ("Does not match template: " ++ fn) $ 36 | ("test" `isPrefixOf` fn) && (".txt" `isSuffixOf` fn) 37 | assertBool (fp ++ " is not in the right directory " ++ sys_tmp_dir) $ 38 | takeDirectory fp `equalFilePath` sys_tmp_dir 39 | hClose fh 40 | assertBool "File does not exist" =<< doesFileExist fp 41 | #ifndef mingw32_HOST_OS 42 | status <- getFileStatus fp 43 | fileMode status .&. 0o777 @?= 0o666 44 | #endif 45 | removeFile fp 46 | , testCase "withSystemTempFile" $ do 47 | (fp, fh) <- withSystemTempFile "test.txt" $ \fp fh -> do 48 | let fn = takeFileName fp 49 | assertBool ("Does not match template: " ++ fn) $ 50 | ("test" `isPrefixOf` fn) && (".txt" `isSuffixOf` fn) 51 | assertBool (fp ++ " is not in the right directory " ++ sys_tmp_dir) $ 52 | takeDirectory fp `equalFilePath` sys_tmp_dir 53 | assertBool "File not open" =<< hIsOpen fh 54 | hPutStrLn fh "hi" 55 | assertBool "File does not exist" =<< doesFileExist fp 56 | #ifndef mingw32_HOST_OS 57 | status <- getFileStatus fp 58 | fileMode status .&. 0o777 @?= 0o600 59 | #endif 60 | return (fp, fh) 61 | assertBool "File still exists" . not =<< doesFileExist fp 62 | assertBool "File not closed" =<< hIsClosed fh 63 | , testCase "withSystemTempDirectory" $ do 64 | fp <- withSystemTempDirectory "test.dir" $ \fp -> do 65 | let fn = takeFileName fp 66 | assertBool ("Does not match template: " ++ fn) $ 67 | ("test.dir" `isPrefixOf` fn) 68 | assertBool (fp ++ " is not in the right directory " ++ sys_tmp_dir) $ 69 | takeDirectory fp `equalFilePath` sys_tmp_dir 70 | assertBool "Directory does not exist" =<< doesDirectoryExist fp 71 | #ifndef mingw32_HOST_OS 72 | status <- getFileStatus fp 73 | fileMode status .&. 0o777 @?= 0o700 74 | #endif 75 | return fp 76 | assertBool "Directory still exists" . not =<< doesDirectoryExist fp 77 | , testCase "writeSystemTempFile" $ do 78 | fp <- writeSystemTempFile "blah.txt" "hello" 79 | str <- readFile fp 80 | "hello" @?= str 81 | removeFile fp 82 | , testCase "emptySystemTempFile" $ do 83 | fp <- emptySystemTempFile "empty.txt" 84 | assertBool "File doesn't exist" =<< doesFileExist fp 85 | removeFile fp 86 | , testCase "withSystemTempFile returns absolute path" $ do 87 | bracket_ (setEnv "TMPDIR" ".") (unsetEnv "TMPDIR") $ do 88 | withSystemTempFile "temp.txt" $ \fp _ -> 89 | assertBool "Not absolute" $ isAbsolute fp 90 | , testCase "withSystemTempDirectory is not interrupted" $ do 91 | -- this mvar is both a channel to pass the name of the directory 92 | -- and a signal that we finished creating files and are ready 93 | -- to be killed 94 | mvar1 <- newEmptyMVar 95 | -- this mvar signals that the withSystemTempDirectory function 96 | -- returned and we can check whether the directory has survived 97 | mvar2 <- newEmptyMVar 98 | threadId <- forkIO $ 99 | (withSystemTempDirectory "temp.test." $ \dir -> do 100 | replicateM_ 100 $ emptyTempFile dir "file.xyz" 101 | putMVar mvar1 dir 102 | threadDelay $ 10^6 103 | ) `finally` (putMVar mvar2 ()) 104 | dir <- readMVar mvar1 105 | -- start sending exceptions 106 | replicateM_ 10 $ forkIO $ killThread threadId 107 | -- wait for the thread to finish 108 | readMVar mvar2 109 | -- check whether the directory was successfully removed 110 | assertBool "Directory was not removed" . not =<< doesDirectoryExist dir 111 | , testCase "createTempFileName" $ do 112 | let template = "testdir" 113 | -- createTempFileName with some tests 114 | checkedCreateTempFileName = do 115 | fp <- createTempFileName sys_tmp_dir template 116 | let directParent = takeDirectory fp 117 | assertBool ("Parent directory " ++ directParent ++ " does not match template: " ++ template) $ 118 | template `isPrefixOf` takeBaseName directParent 119 | assertBool "Parent directory does not exist" =<< doesDirectoryExist directParent 120 | assertBool "File already exists" . not =<< doesFileExist fp 121 | return fp 122 | 123 | -- Helper for the test just to ensure we don't leave garbage lying 124 | -- around when we run/abort tests. 125 | withTempFileName = bracket checkedCreateTempFileName 126 | (removeDirectoryRecursive . takeDirectory) 127 | 128 | -- Now make some filenames, check that they are all different. Note that 129 | -- we run the tests we defined above on each one. We don't want to plug 130 | -- a huge number here to not hit any file resource limits. 131 | let checkDifferent 0 fns = assertBool "Got duplicate temporary filenames" 132 | . all ((== 1) . length) . group $ sort fns 133 | checkDifferent n fns = withTempFileName $ \fp -> 134 | checkDifferent (n - 1) (fp : fns) 135 | 136 | checkDifferent (50 :: Int) [] 137 | ] 138 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This is the complex Travis configuration, which is intended for use 2 | # on open source libraries which need compatibility across multiple GHC 3 | # versions, must work with cabal-install, and should be 4 | # cross-platform. For more information and other options, see: 5 | # 6 | # https://docs.haskellstack.org/en/stable/travis_ci/ 7 | # 8 | # Copy these contents into the root directory of your Github project in a file 9 | # named .travis.yml 10 | 11 | # Use new container infrastructure to enable caching 12 | sudo: false 13 | 14 | # Do not choose a language; we provide our own build tools. 15 | language: generic 16 | 17 | # Caching so the next build will be fast too. 18 | cache: 19 | directories: 20 | - $HOME/.ghc 21 | - $HOME/.cabal 22 | - $HOME/.stack 23 | 24 | # The different configurations we want to test. We have BUILD=cabal which uses 25 | # cabal-install, and BUILD=stack which uses Stack. More documentation on each 26 | # of those below. 27 | # 28 | # We set the compiler values here to tell Travis to use a different 29 | # cache file per set of arguments. 30 | # 31 | # If you need to have different apt packages for each combination in the 32 | # matrix, you can use a line such as: 33 | # addons: {apt: {packages: [libfcgi-dev,libgmp-dev]}} 34 | matrix: 35 | include: 36 | # We grab the appropriate GHC and cabal-install versions from hvr's PPA. See: 37 | # https://github.com/hvr/multi-ghc-travis 38 | - env: BUILD=cabal GHCVER=7.0.4 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 39 | compiler: ": #GHC 7.0.4" 40 | addons: {apt: {packages: [cabal-install-1.16,ghc-7.0.4], sources: [hvr-ghc]}} 41 | - env: BUILD=cabal GHCVER=7.2.2 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 42 | compiler: ": #GHC 7.2.2" 43 | addons: {apt: {packages: [cabal-install-1.16,ghc-7.2.2], sources: [hvr-ghc]}} 44 | - env: BUILD=cabal GHCVER=7.4.2 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 45 | compiler: ": #GHC 7.4.2" 46 | addons: {apt: {packages: [cabal-install-1.16,ghc-7.4.2], sources: [hvr-ghc]}} 47 | - env: BUILD=cabal GHCVER=7.6.3 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 48 | compiler: ": #GHC 7.6.3" 49 | addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3], sources: [hvr-ghc]}} 50 | - env: BUILD=cabal GHCVER=7.8.4 CABALVER=1.18 HAPPYVER=1.19.5 ALEXVER=3.1.7 51 | compiler: ": #GHC 7.8.4" 52 | addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}} 53 | - env: BUILD=cabal GHCVER=7.10.3 CABALVER=1.22 HAPPYVER=1.19.5 ALEXVER=3.1.7 54 | compiler: ": #GHC 7.10.3" 55 | addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}} 56 | - env: BUILD=cabal GHCVER=8.0.2 CABALVER=1.24 HAPPYVER=1.19.5 ALEXVER=3.1.7 57 | compiler: ": #GHC 8.0.2" 58 | addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}} 59 | 60 | # Build with the newest GHC and cabal-install. This is an accepted failure, 61 | # see below. 62 | - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 63 | compiler: ": #GHC HEAD" 64 | addons: {apt: {packages: [cabal-install-head,ghc-head], sources: [hvr-ghc]}} 65 | 66 | # The Stack builds. We can pass in arbitrary Stack arguments via the ARGS 67 | # variable, such as using --stack-yaml to point to a different file. 68 | - env: BUILD=stack ARGS="" 69 | compiler: ": #stack default" 70 | addons: {apt: {packages: [libgmp-dev]}} 71 | 72 | - env: BUILD=stack ARGS="--resolver lts-2" 73 | compiler: ": #stack 7.8.4" 74 | addons: {apt: {packages: [libgmp-dev]}} 75 | 76 | - env: BUILD=stack ARGS="--resolver lts-3" 77 | compiler: ": #stack 7.10.2" 78 | addons: {apt: {packages: [libgmp-dev]}} 79 | 80 | - env: BUILD=stack ARGS="--resolver lts-6" 81 | compiler: ": #stack 7.10.3" 82 | addons: {apt: {packages: [libgmp-dev]}} 83 | 84 | - env: BUILD=stack ARGS="--resolver lts-7" 85 | compiler: ": #stack 8.0.1" 86 | addons: {apt: {packages: [libgmp-dev]}} 87 | 88 | - env: BUILD=stack ARGS="--resolver lts-8" 89 | compiler: ": #stack 8.0.2" 90 | addons: {apt: {packages: [libgmp-dev]}} 91 | 92 | # Nightly builds are allowed to fail 93 | - env: BUILD=stack ARGS="--resolver nightly" 94 | compiler: ": #stack nightly" 95 | addons: {apt: {packages: [libgmp-dev]}} 96 | 97 | allow_failures: 98 | - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 99 | - env: BUILD=stack ARGS="--resolver nightly" 100 | 101 | before_install: 102 | # Using compiler above sets CC to an invalid value, so unset it 103 | - unset CC 104 | 105 | # We want to always allow newer versions of packages when building on GHC HEAD 106 | - CABALARGS="" 107 | - if [ "x$GHCVER" = "xhead" ]; then CABALARGS=--allow-newer; fi 108 | 109 | # Download and unpack the stack executable 110 | - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH 111 | - mkdir -p ~/.local/bin 112 | - | 113 | if [ `uname` = "Darwin" ] 114 | then 115 | travis_retry curl --insecure -L https://www.stackage.org/stack/osx-x86_64 | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin 116 | else 117 | travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' 118 | fi 119 | 120 | # Use the more reliable S3 mirror of Hackage 121 | mkdir -p $HOME/.cabal 122 | echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config 123 | echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config 124 | 125 | if [ "$CABALVER" != "1.16" ] 126 | then 127 | echo 'jobs: $ncpus' >> $HOME/.cabal/config 128 | fi 129 | 130 | install: 131 | - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" 132 | - if [ -f configure.ac ]; then autoreconf -i; fi 133 | - | 134 | set -ex 135 | case "$BUILD" in 136 | stack) 137 | stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies 138 | ;; 139 | cabal) 140 | cabal --version 141 | travis_retry cabal update 142 | 143 | # tasty does not support ghc 7.0 and 7.2 144 | case "$GHCVER" in 145 | 7.0.4|7.2.2) 146 | ENABLE_TESTS='' ;; 147 | *) 148 | ENABLE_TESTS='--enable-tests' ;; 149 | esac 150 | 151 | # Get the list of packages from the stack.yaml file 152 | PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@') 153 | 154 | cabal install --only-dependencies $ENABLE_TESTS --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES 155 | ;; 156 | esac 157 | set +ex 158 | 159 | script: 160 | - | 161 | set -ex 162 | case "$BUILD" in 163 | stack) 164 | stack --no-terminal $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps 165 | ;; 166 | cabal) 167 | 168 | # tasty does not support ghc 7.0 and 7.2 169 | case "$GHCVER" in 170 | 7.0.4|7.2.2) 171 | ENABLE_TESTS='' ;; 172 | *) 173 | ENABLE_TESTS='--enable-tests' ;; 174 | esac 175 | 176 | 177 | cabal install $ENABLE_TESTS --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES 178 | 179 | ORIGDIR=$(pwd) 180 | for dir in $PACKAGES 181 | do 182 | cd $dir 183 | cabal check || [ "$CABALVER" == "1.16" ] 184 | cabal sdist 185 | PKGVER=$(cabal info . | awk '{print $2;exit}') 186 | SRC_TGZ=$PKGVER.tar.gz 187 | cd dist 188 | tar zxfv "$SRC_TGZ" 189 | cd "$PKGVER" 190 | cabal configure $ENABLE_TESTS 191 | cabal build 192 | [ -n "$ENABLE_TESTS" ] && cabal test 193 | cd $ORIGDIR 194 | done 195 | ;; 196 | esac 197 | set +ex 198 | -------------------------------------------------------------------------------- /System/IO/Temp.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, ScopedTypeVariables #-} 2 | -- | Functions to create temporary files and directories. 3 | -- 4 | -- Most functions come in two flavours: those that create files/directories 5 | -- under the system standard temporary directory and those that use the 6 | -- user-supplied directory. 7 | -- 8 | -- The functions that create files/directories under the system standard 9 | -- temporary directory will return canonical absolute paths (see 10 | -- 'getCanonicalTemporaryDirectory'). The functions use the user-supplied 11 | -- directory do not canonicalize the returned path. 12 | -- 13 | -- The action inside 'withTempFile' or 'withTempDirectory' is allowed to 14 | -- remove the temporary file/directory if it needs to. 15 | -- 16 | -- == Templates and file names 17 | -- 18 | -- The treatment of templates differs somewhat for files vs directories. 19 | -- 20 | -- For files, the template has form @name.ext@, and a random number will be 21 | -- placed between between the name and the extension to yield a unique file 22 | -- name, e.g. @name1804289383846930886.ext@. 23 | -- 24 | -- For directories, no extension is recognized. 25 | -- A random hexadecimal string (whose length depends on the system's word 26 | -- size) is appended to the end of the template. 27 | -- For instance, 28 | -- the directory template @dir@ may result in a directory named 29 | -- @dir-e4bd89e5d00acdee@. 30 | -- 31 | -- You shouldn't rely on the specific form of file or directory names 32 | -- generated by the library; it has changed in the past and may change in the future. 33 | module System.IO.Temp ( 34 | withSystemTempFile, withSystemTempDirectory, 35 | withTempFile, withTempDirectory, 36 | openNewBinaryFile, 37 | createTempDirectory, 38 | writeTempFile, writeSystemTempFile, 39 | emptyTempFile, emptySystemTempFile, 40 | createTempFileName, 41 | withTempFileName, 42 | -- * Re-exports from System.IO 43 | openTempFile, 44 | openBinaryTempFile, 45 | -- * Auxiliary functions 46 | getCanonicalTemporaryDirectory 47 | ) where 48 | 49 | import qualified Control.Monad.Catch as MC 50 | 51 | import Control.Monad.IO.Class 52 | import Data.Bits -- no import list: we use different functions 53 | -- depending on the base version 54 | #if !MIN_VERSION_base(4,8,0) 55 | import Data.Word (Word) 56 | #endif 57 | import System.Directory 58 | import System.IO (Handle, hClose, openTempFile, openBinaryTempFile, 59 | openBinaryTempFileWithDefaultPermissions, hPutStr) 60 | import System.IO.Error (isAlreadyExistsError) 61 | import System.FilePath (()) 62 | import System.Random 63 | #ifdef mingw32_HOST_OS 64 | import System.Directory ( createDirectory ) 65 | #else 66 | import qualified System.Posix 67 | #endif 68 | import Text.Printf 69 | 70 | -- | Create, open, and use a temporary file in the system standard temporary directory. 71 | -- 72 | -- The temp file is deleted after use. 73 | -- 74 | -- Behaves exactly the same as 'withTempFile', except that the parent temporary directory 75 | -- will be that returned by 'getCanonicalTemporaryDirectory'. 76 | withSystemTempFile :: (MonadIO m, MC.MonadMask m) => 77 | String -- ^ File name template 78 | -> (FilePath -> Handle -> m a) -- ^ Callback that can use the file 79 | -> m a 80 | withSystemTempFile template action = liftIO getCanonicalTemporaryDirectory >>= \tmpDir -> withTempFile tmpDir template action 81 | 82 | -- | Create and use a temporary directory in the system standard temporary directory. 83 | -- 84 | -- Behaves exactly the same as 'withTempDirectory', except that the parent temporary directory 85 | -- will be that returned by 'getCanonicalTemporaryDirectory'. 86 | withSystemTempDirectory :: (MonadIO m, MC.MonadMask m) => 87 | String -- ^ Directory name template 88 | -> (FilePath -> m a) -- ^ Callback that can use the directory 89 | -> m a 90 | withSystemTempDirectory template action = liftIO getCanonicalTemporaryDirectory >>= \tmpDir -> withTempDirectory tmpDir template action 91 | 92 | 93 | -- | Create, open, and use a temporary file in the given directory. 94 | -- 95 | -- The temp file is deleted after use. 96 | withTempFile :: (MonadIO m, MC.MonadMask m) => 97 | FilePath -- ^ Parent directory to create the file in 98 | -> String -- ^ File name template 99 | -> (FilePath -> Handle -> m a) -- ^ Callback that can use the file 100 | -> m a 101 | withTempFile tmpDir template action = 102 | MC.bracket 103 | (liftIO (openTempFile tmpDir template)) 104 | (\(name, handle) -> liftIO (hClose handle >> ignoringIOErrors (removeFile name))) 105 | (uncurry action) 106 | 107 | -- | Create and use a temporary directory inside the given directory. 108 | -- 109 | -- The directory is deleted after use. 110 | withTempDirectory :: (MC.MonadMask m, MonadIO m) => 111 | FilePath -- ^ Parent directory to create the directory in 112 | -> String -- ^ Directory name template 113 | -> (FilePath -> m a) -- ^ Callback that can use the directory 114 | -> m a 115 | withTempDirectory targetDir template = 116 | MC.bracket 117 | (liftIO (createTempDirectory targetDir template)) 118 | (liftIO . ignoringIOErrors . removeDirectoryRecursive) 119 | 120 | -- | Create a unique new file, write (text mode) a given data string to it, 121 | -- and close the handle again. The file will not be deleted automatically, 122 | -- and only the current user will have permission to access the file. 123 | -- 124 | -- @since 1.2.1 125 | writeTempFile :: FilePath -- ^ Parent directory to create the file in 126 | -> String -- ^ File name template 127 | -> String -- ^ Data to store in the file 128 | -> IO FilePath -- ^ Path to the (written and closed) file 129 | writeTempFile targetDir template content = MC.bracket 130 | (openTempFile targetDir template) 131 | (\(_, handle) -> hClose handle) 132 | (\(filePath, handle) -> hPutStr handle content >> return filePath) 133 | 134 | -- | Like 'writeTempFile', but use the system directory for temporary files. 135 | -- 136 | -- @since 1.2.1 137 | writeSystemTempFile :: String -- ^ File name template 138 | -> String -- ^ Data to store in the file 139 | -> IO FilePath -- ^ Path to the (written and closed) file 140 | writeSystemTempFile template content 141 | = getCanonicalTemporaryDirectory >>= \tmpDir -> writeTempFile tmpDir template content 142 | 143 | -- | Create a unique new empty file. (Equivalent to 'writeTempFile' with empty data string.) 144 | -- This is useful if the actual content is provided by an external process. 145 | -- 146 | -- @since 1.2.1 147 | emptyTempFile :: FilePath -- ^ Parent directory to create the file in 148 | -> String -- ^ File name template 149 | -> IO FilePath -- ^ Path to the (written and closed) file 150 | emptyTempFile targetDir template = MC.bracket 151 | (openTempFile targetDir template) 152 | (\(_, handle) -> hClose handle) 153 | (\(filePath, _) -> return filePath) 154 | 155 | -- | Like 'emptyTempFile', but use the system directory for temporary files. 156 | -- 157 | -- @since 1.2.1 158 | emptySystemTempFile :: String -- ^ File name template 159 | -> IO FilePath -- ^ Path to the (written and closed) file 160 | emptySystemTempFile template 161 | = getCanonicalTemporaryDirectory >>= \tmpDir -> emptyTempFile tmpDir template 162 | 163 | 164 | ignoringIOErrors :: MC.MonadCatch m => m () -> m () 165 | ignoringIOErrors ioe = ioe `MC.catch` (\e -> const (return ()) (e :: IOError)) 166 | 167 | -- | Like 'openBinaryTempFile', but uses 666 rather than 600 for the 168 | -- permissions. 169 | -- 170 | -- Equivalent to 'openBinaryTempFileWithDefaultPermissions'. 171 | openNewBinaryFile :: FilePath -> String -> IO (FilePath, Handle) 172 | openNewBinaryFile = openBinaryTempFileWithDefaultPermissions 173 | 174 | -- | Create a temporary directory. 175 | createTempDirectory 176 | :: FilePath -- ^ Parent directory to create the directory in 177 | -> String -- ^ Directory name template 178 | -> IO FilePath 179 | createTempDirectory dir template = findTempName 180 | where 181 | findTempName = do 182 | x :: Word <- randomIO 183 | let dirpath = dir template ++ printf "-%.*x" (wordSize `div` 4) x 184 | r <- MC.try $ mkPrivateDir dirpath 185 | case r of 186 | Right _ -> return dirpath 187 | Left e | isAlreadyExistsError e -> findTempName 188 | | otherwise -> ioError e 189 | 190 | -- | Get a temporary file name without creating a file. 191 | -- 192 | -- If you merely want to create a temporary file or directory, 193 | -- please use one of the functions above, such as 'withTempFile' or 194 | -- 'withTempDirectory'. 195 | -- 196 | -- This function can be useful when: 197 | -- 198 | -- * you want to create a file of a special type, like a FIFO or a socket. 199 | -- * you have a process/function that accepts a filename and then waits for it 200 | -- to appear to do something, so you need to know the file name /before/ the 201 | -- file is created. 202 | -- * you need a target for an atomic rename operation. 203 | -- 204 | -- This function works by creating a temporary directory with 205 | -- 'createTempDirectory' and then returning a fixed file name within that 206 | -- directory. On UNIX, the directory is created with the mode 0700, which 207 | -- ensures that a different user cannot make us overwrite an existing file. 208 | -- This makes this function more secure than merely generating a random file 209 | -- name. See the discussions at 210 | -- and 211 | -- . 212 | -- 213 | -- See also 'withTempFileName'. 214 | createTempFileName 215 | :: FilePath -- ^ Parent directory to create the temporary directory in 216 | -> String -- ^ Directory name template 217 | -> IO FilePath 218 | createTempFileName dir template = do 219 | tempDir <- createTempDirectory dir template 220 | return (tempDir "temp_file") 221 | 222 | -- | Similarly to 'createTempFileName', this function creates a temporary 223 | -- directory and constructs a fixed file name within it. The supplied callback is 224 | -- called with the generated file name, and after it returns, the directory is 225 | -- removed with all its contents. 226 | -- 227 | -- Please read the documentation for 'createTempFileName' carefully and make 228 | -- sure this is the right function for your needs before using it. 229 | withTempFileName 230 | :: (MonadIO m, MC.MonadMask m) 231 | => FilePath -- ^ Parent directory to create the temporary directory in 232 | -> String -- ^ Directory name template 233 | -> (FilePath -> m a) 234 | -> m a 235 | withTempFileName dir template k = withTempDirectory dir template $ \tempDir -> 236 | k (tempDir "temp_file") 237 | 238 | -- | Word size in bits 239 | wordSize :: Int 240 | wordSize = 241 | #if MIN_VERSION_base(4,7,0) 242 | finiteBitSize (undefined :: Word) 243 | #else 244 | bitSize (undefined :: Word) 245 | #endif 246 | 247 | mkPrivateDir :: String -> IO () 248 | #ifdef mingw32_HOST_OS 249 | mkPrivateDir s = createDirectory s 250 | #else 251 | mkPrivateDir s = System.Posix.createDirectory s 0o700 252 | #endif 253 | 254 | -- | Return the absolute and canonical path to the system temporary 255 | -- directory. 256 | -- 257 | -- >>> setCurrentDirectory "/home/feuerbach/" 258 | -- >>> setEnv "TMPDIR" "." 259 | -- >>> getTemporaryDirectory 260 | -- "." 261 | -- >>> getCanonicalTemporaryDirectory 262 | -- "/home/feuerbach" 263 | getCanonicalTemporaryDirectory :: IO FilePath 264 | getCanonicalTemporaryDirectory = getTemporaryDirectory >>= canonicalizePath 265 | --------------------------------------------------------------------------------