├── .gitignore ├── cache └── .gitignore ├── deps ├── .gitignore └── build.jl ├── REQUIRE ├── sources.list ├── LICENSE.md ├── src ├── winrpm_bindeps.jl └── WinRPM.jl └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | installed.list 2 | -------------------------------------------------------------------------------- /cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /deps/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !build.jl 4 | -------------------------------------------------------------------------------- /deps/build.jl: -------------------------------------------------------------------------------- 1 | using WinRPM 2 | WinRPM.update() 3 | -------------------------------------------------------------------------------- /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.4 2 | Compat 0.10.0 3 | URIParser 0.0.3 4 | @unix HTTPClient 0.0.0 5 | LibExpat 0.0.3 6 | Libz 7 | BinDeps 0.3 8 | SHA 9 | -------------------------------------------------------------------------------- /sources.list: -------------------------------------------------------------------------------- 1 | https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win32/openSUSE_42.2 2 | https://cache.julialang.org/http://download.opensuse.org/repositories/windows:/mingw:/win64/openSUSE_42.2 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The WinRPM package installation library is licensed under the MIT License: 2 | 3 | > Copyright (c) 2013-2014: Jameson Nash and other contributors: 4 | > 5 | > https://github.com/JuliaLang/WinRPM.jl/contributors 6 | > 7 | > Permission is hereby granted, free of charge, to any person obtaining 8 | > a copy of this software and associated documentation files (the 9 | > "Software"), to deal in the Software without restriction, including 10 | > without limitation the rights to use, copy, modify, merge, publish, 11 | > distribute, sublicense, and/or sell copies of the Software, and to 12 | > permit persons to whom the Software is furnished to do so, subject to 13 | > the following conditions: 14 | > 15 | > The above copyright notice and this permission notice shall be 16 | > included in all copies or substantial portions of the Software. 17 | > 18 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /src/winrpm_bindeps.jl: -------------------------------------------------------------------------------- 1 | # BinDeps integration 2 | 3 | using BinDeps 4 | import BinDeps: PackageManager, can_use, package_available, available_version, 5 | libdir, generate_steps, LibraryDependency, provider, provides, pkg_name 6 | 7 | update_once = true 8 | 9 | type RPM <: PackageManager 10 | package 11 | end 12 | 13 | can_use(::Type{RPM}) = is_windows() 14 | function package_available(p::RPM) 15 | global update_once 16 | !can_use(RPM) && return false 17 | pkgs = p.package 18 | if isa(pkgs, AbstractString) 19 | pkgs = [pkgs] 20 | end 21 | if (update_once::Bool) 22 | info("Updating WinRPM package list") 23 | update() 24 | update_once = false 25 | end 26 | return all(pkg -> (length(lookup(pkg).p) > 0), pkgs) 27 | end 28 | 29 | available_version(p::RPM) = lookup(p.package).p[1][xpath"version/@ver"][1] 30 | libdir(p::RPM,dep) = joinpath(dirname(dirname(@__FILE__)), "deps", "usr", "$(Sys.ARCH)-w64-mingw32", "sys-root", "mingw", "bin") 31 | pkg_name(p::RPM) = p.package 32 | 33 | provider{T<:AbstractString}(::Type{RPM}, packages::Vector{T}; opts...) = RPM(packages) 34 | provides(::Type{RPM}, packages::AbstractArray, dep::LibraryDependency; opts...) = 35 | provides(provider(RPM, packages; opts...), dep; opts...) 36 | 37 | function generate_steps(dep::LibraryDependency, h::RPM, opts) 38 | if get(opts, :force_rebuild, false) 39 | error("Will not force WinRPM to rebuild dependency \"$(dep.name)\".\n"* 40 | "Please make any necessary adjustments manually (This might just be a version upgrade)") 41 | end 42 | () -> install(h.package; yes=true) 43 | end 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Changelog: This version of function "download()" in file "WinRPM.jl" uses powershell instead to avoid downloading errors. 2 | 3 | How to use: 4 | 5 | ```julia 6 | Pkg.rm("WinRPM") 7 | Pkg.clone("https://github.com/cafe/WinRPM.jl.git") 8 | ``` 9 | 10 | Introduction 11 | ============ 12 | 13 | WinRPM is an installer for RPM packages provided by an RPM-md build system. 14 | The default RPM-md provider is the [OpenSUSE build service](https://build.opensuse.org/), 15 | which builds 32- and 64-bit DLLs for libraries used by 16 | several Julia packages (note: builds are cross-compiled). 17 | 18 | Installation 19 | ------------ 20 | 21 | To install WinRPM via the Julia package manager, use: 22 | 23 | ```julia 24 | Pkg.add("WinRPM") 25 | ``` 26 | 27 | Package Availability 28 | -------------------- 29 | 30 | To search for a package from within Julia: 31 | 32 | ```julia 33 | using WinRPM 34 | 35 | WinRPM.search("packagename") 36 | ``` 37 | 38 | See also: upstream package information for [Win64](https://build.opensuse.org/project/show/windows%3Amingw%3Awin64) 39 | and [Win32](https://build.opensuse.org/project/show/windows%3Amingw%3Awin32) 40 | 41 | Package Installation 42 | -------------------- 43 | 44 | To install a library using WinRPM: 45 | 46 | ```julia 47 | WinRPM.install("gtk2") 48 | WinRPM.install("win_iconv","mingw32") 49 | ``` 50 | 51 | Dependencies 52 | ------------ 53 | 54 | WinRPM will automatically install dependencies declared in the RPM-md package specification. 55 | 56 | Package Creation 57 | ---------------- 58 | 59 | Please see the OpenSUSE build service [packaging guidelines](http://en.opensuse.org/openSUSE:Packaging_guidelines) 60 | for further information. 61 | 62 | BinDeps Integration 63 | =================== 64 | 65 | WinRPM may be integrated with the [BinDeps](https://github.com/JuliaLang/BinDeps.jl) 66 | system by declaring a `provides(WinRPM.RPM...` line for each serviceable dependency. 67 | 68 | For example, in the [Tk.jl](https://github.com/JuliaLang/Tk.jl) 69 | package the following lines declare availability of the `tcl` and `tk` libraries 70 | from WinRPM: 71 | 72 | ```julia 73 | @windows_only begin 74 | using WinRPM 75 | provides(WinRPM.RPM,"tk",tk,os = :Windows) 76 | provides(WinRPM.RPM,"tcl",tcl,os = :Windows) 77 | end 78 | ``` 79 | 80 | These lines must be preceded by `BinDeps.library_dependency` declarations; 81 | please see the BinDeps documentation for more information. 82 | 83 | It may also be helpful to review usage examples in Tk.jl or other existing packages 84 | (see `deps/build.jl`): [Nettle.jl](https://github.com/staticfloat/Nettle.jl) 85 | [Cairo.jl](https://github.com/JuliaLang/Cairo.jl) 86 | 87 | 88 | Stand-alone Usage 89 | ================= 90 | 91 | For stand-alone use, add the following lines to your `%APPDATA%/julia/.juliarc.jl` file: 92 | 93 | ```julia 94 | RPMbindir = Pkg.dir("WinRPM","deps","usr","$(Sys.ARCH)-w64-mingw32","sys-root","mingw","bin") 95 | push!(DL_LOAD_PATH,RPMbindir) 96 | ENV["PATH"]=ENV["PATH"]*";"*RPMbindir 97 | ``` 98 | 99 | Full API 100 | ======== 101 | 102 | RPM-md provides the following functions for general usage: 103 | `update`, `whatprovides`, `search`, `lookup`, and `install` 104 | 105 | `update()` -- download the new metadata from the hosts. Additional hosts can be added by editing the file `sources.list`. 106 | 107 | `whatprovides(file)` -- given a part of a filename or file-path, returns a list of packages that include 108 | 109 | `search(string)` -- search for a string in the package description, summary, or name fields and returns a list of matching packages 110 | 111 | `lookup(name)` -- search for a package by name 112 | 113 | `install(pkg)` -- install a package (by name or package identifier), including dependencies, into the `deps` folder 114 | 115 | The functions typically take a second parameter "arch" specifying the package architecture for search, defaulting to the current operating system. 116 | It also offers the keyword argument `yes` which should be set to `true` if no prompt is desired. 117 | 118 | Usage Example 119 | ============= 120 | 121 | Package lists can be further filtered and analyzed, as the following example demonstrates: 122 | 123 | ```julia 124 | julia> using WinRPM 125 | 126 | julia> gtk3_candidates = WinRPM.search("gtk3", "mingw32") 127 | 1. webkitgtk3-debug (mingw32) - Debug information for package mingw32-webkitgtk3 128 | 2. webkitgtk3-lang (mingw32) - Languages for package mingw32-webkitgtk3 129 | 3. webkitgtk3-tools (mingw32) - Library for rendering web content, GTK+ 3 Port (tools) 130 | 4. gtk3-data (mingw32) - The GTK+ toolkit library (version 3) -- Data Files 131 | 5. gtk3-lang (mingw32) - Languages for package mingw32-gtk3 132 | 6. gtk3 (mingw32) - The GTK+ toolkit library (version 3) 133 | 7. gtk3-devel (mingw32) - The GTK+ toolkit library (version 3) -- Development Files 134 | 8. gtk3-debug (mingw32) - Debug information for package mingw32-gtk3 135 | 9. gtk3-tools (mingw32) - The GTK+ toolkit library (version 3) -- Tools 136 | 10. libwebkitgtk3 (mingw32) - Library for rendering web content, GTK+ 3 Port 137 | 11. libwebkitgtk3-devel (mingw32) - Library for rendering web content, GTK+ 3 Port (development files) 138 | 139 | julia> gtk3_pkg = gtk3_candidates[6] 140 | Name: gtk3 141 | Summary: The GTK+ toolkit library (version 3) 142 | Version: 3.8.1 (rel 1.31) 143 | Arch: mingw32 144 | URL: http://www.gtk.org/ 145 | License: LGPL-2.0+ 146 | Description: GTK+ is a multi-platform toolkit for creating graphical user interfaces. 147 | Offering a complete set of widgets, GTK+ is suitable for projects 148 | ranging from small one-off projects to complete application suites. 149 | 150 | julia> WinRPM.install(gtk3_pkg) 151 | MESSAGE: Installing: libxml2, atk, gdk-pixbuf, liblzma, zlib, libpng, libtiff, pixman, freetype, libffi, glib2-lang, atk-lang, libjpeg, gdk-pixbuf-lang, libharfbuzz, glib2, fontconfig, libcairo2, libjasper, libgcc, libintl, gtk3 152 | MESSAGE: Downloading: libxml2 153 | MESSAGE: Extracting: libxml2 154 | 2286 blocks 155 | MESSAGE: Downloading: atk 156 | MESSAGE: Extracting: atk 157 | 263 blocks 158 | ... 159 | MESSAGE: Downloading: gtk3 160 | MESSAGE: Extracting: gtk3 161 | 9614 blocks 162 | MESSAGE: Success 163 | 164 | julia> # or we can just install it directly 165 | julia> WinRPM.install("gtk3") 166 | ``` 167 | -------------------------------------------------------------------------------- /src/WinRPM.jl: -------------------------------------------------------------------------------- 1 | module WinRPM 2 | 3 | using Compat 4 | using Compat: String, KERNEL 5 | 6 | if is_unix() 7 | using HTTPClient.HTTPC 8 | end 9 | 10 | using Libz, LibExpat, URIParser 11 | 12 | import Base: show, getindex, wait_close, pipeline_error 13 | 14 | #export update, whatprovides, search, lookup, install, deps, help 15 | 16 | if is_windows() 17 | const OS_ARCH = Sys.WORD_SIZE == 64 ? "mingw64" : "mingw32" 18 | else 19 | const OS_ARCH = string(Sys.ARCH) 20 | end 21 | 22 | function mkdirs(dir) 23 | if !isdir(dir) 24 | mkdir(dir) 25 | end 26 | end 27 | 28 | global const packages = ETree[] 29 | 30 | function __init__() 31 | empty!(packages) 32 | global cachedir = joinpath(dirname(dirname(@__FILE__)), "cache") 33 | global installdir = joinpath(dirname(dirname(@__FILE__)), "deps") 34 | global indexpath = joinpath(cachedir, "index") 35 | 36 | mkdirs(cachedir) 37 | mkdirs(installdir) 38 | open(joinpath(dirname(@__FILE__), "..", "sources.list")) do f 39 | global sources = filter!(map(chomp, readlines(f))) do l 40 | return !isempty(l) && l[1] != '#' 41 | end 42 | end 43 | global installedlist = joinpath(dirname(@__FILE__), "..", "installed.list") 44 | update(false, false) 45 | end 46 | 47 | if is_unix() 48 | function download(source::AbstractString) 49 | x = HTTPC.get(source) 50 | unsafe_string(x.body), x.http_code 51 | end 52 | elseif is_windows() 53 | function psdownload(url, to) 54 | run(`powershell (new-object system.net.webClient).downloadFile(\"$url\", \"$to\")`) 55 | return to 56 | end 57 | psdownload(url) = psdownload(url, tempname()) 58 | 59 | function download(source::AbstractString; retry=5) 60 | dest = Vector{UInt16}(261) 61 | for i in 1:retry 62 | try 63 | filename = psdownload(source) 64 | return readstring(filename), 200 65 | end 66 | warn("Unknown download failure") 67 | warn("Retry $i/$retry downloading: $source") 68 | end 69 | return "", 0 70 | end 71 | else 72 | error("Platform not supported: $(KERNEL)") 73 | end 74 | 75 | getcachedir(source) = getcachedir(cachedir, source) 76 | function getcachedir(cachedir, source) 77 | open(indexpath, isfile(indexpath)?"r+":"w+") do cacheindex 78 | seek(cacheindex,0) 79 | lines = readlines(cacheindex) 80 | for (idx,line) in enumerate(lines) 81 | if !startswith(line,'#') && ' ' in line 82 | stri, src = split(chomp(line), ' ', limit=2) 83 | cache = joinpath(cachedir, stri) 84 | if !isdir(cache) 85 | # remove this directory from the list, 86 | # write out the new cacheindex file 87 | # and restart 88 | seek(cacheindex, 0) 89 | truncate(cacheindex, 0) 90 | for (idx2,l) in enumerate(lines) 91 | if idx == idx2 92 | write(cacheindex, '#') 93 | end 94 | write(cacheindex, l) 95 | end 96 | empty!(lines) 97 | return getcachedir(source) 98 | end 99 | if source == src 100 | return cache 101 | end 102 | end 103 | end 104 | # not found in existing cache 105 | i = 0 106 | local stri, cache 107 | while true 108 | i += 1 109 | stri = string(i) 110 | cache = joinpath(cachedir, stri) 111 | if !ispath(cache) 112 | try 113 | mkdir(cache) 114 | break 115 | end 116 | end 117 | end 118 | seekend(cacheindex) 119 | println(cacheindex, stri, ' ', source) 120 | flush(cacheindex) 121 | return cache 122 | end 123 | end 124 | 125 | function update(ignorecache::Bool=false, allow_remote::Bool=true) 126 | global sources, packages 127 | empty!(packages) 128 | for source in sources 129 | if source == "" || '\r' in source || '\n' in source 130 | continue 131 | end 132 | cache = getcachedir(source) 133 | function cacheget(path::AbstractString, never_cache::Bool) 134 | gunzip = false 135 | path2 = joinpath(cache,escape(path)) 136 | if endswith(path2, ".gz") 137 | path2 = path2[1:end-3] 138 | gunzip = true 139 | end 140 | if !(ignorecache || (never_cache && allow_remote)) && isfile(path2) 141 | return readstring(path2) 142 | end 143 | if !allow_remote 144 | warn("skipping $path, not in cache -- call WinRPM.update() to download") 145 | return nothing 146 | end 147 | info("Downloading $source/$path") 148 | data = download("$source/$path") 149 | if data[2] != 200 150 | warn("received error $(data[2]) while downloading $source/$path") 151 | return nothing 152 | end 153 | body = gunzip ? Libz.decompress(convert(Vector{UInt8},data[1])) : data[1] 154 | open(path2, "w") do f 155 | write(f, body) 156 | end 157 | return String(body) 158 | end 159 | repomd = cacheget("repodata/repomd.xml", true) 160 | if repomd === nothing 161 | continue 162 | end 163 | xml = xp_parse(repomd) 164 | try 165 | for data = xml[xpath"/repomd/data[@type='primary']"] 166 | primary = cacheget(data[xpath"location/@href"][1], false) 167 | if primary === nothing 168 | continue 169 | end 170 | pkgs = xp_parse(primary)[xpath"package[@type='rpm']"] 171 | pkgs["/"][1].attr["url"] = source 172 | for pkg in pkgs[xpath".[arch='noarch' or arch='src'][starts-with(name,'mingw32-') or starts-with(name, 'mingw64-')]"] 173 | name = pkg[xpath"name"][1] 174 | arch = pkg[xpath"arch"][1] 175 | new_arch, new_name = split(LibExpat.string_value(name), '-', limit=2) 176 | old_arch = LibExpat.string_value(arch) 177 | if old_arch != "noarch" 178 | new_arch = "$new_arch-$old_arch" 179 | end 180 | push!(empty!(name.elements), new_name) 181 | push!(empty!(arch.elements), new_arch) 182 | end 183 | append!(packages,pkgs) 184 | end 185 | catch err 186 | warn("encounted invalid data while parsing repomd") 187 | rethrow(err) 188 | continue 189 | end 190 | end 191 | end 192 | 193 | immutable Package 194 | pd::ETree 195 | end 196 | Package(p::Package) = p 197 | Package(p::Vector{ETree}) = [Package(pkg) for pkg in p] 198 | 199 | getindex(pkg::Package,x) = getindex(pkg.pd, x) 200 | 201 | immutable Packages{T<:Union{Set{ETree},Vector{ETree}}} 202 | p::T 203 | end 204 | Packages(pkgs::Vector{Package}) = Packages([p.pd for p in pkgs]) 205 | Packages(xpath::LibExpat.XPath) = Packages(packages[xpath]) 206 | 207 | getindex(pkg::Packages,x) = Package(getindex(pkg.p,x)) 208 | Base.length(pkg::Packages) = length(pkg.p) 209 | Base.isempty(pkg::Packages) = isempty(pkg.p) 210 | Base.start(pkg::Packages) = start(pkg.p) 211 | Base.next(pkg::Packages,x) = ((p,s)=next(pkg.p,x); (Package(p),s)) 212 | Base.done(pkg::Packages,x) = done(pkg.p,x) 213 | 214 | function show(io::IO, pkg::Package) 215 | println(io,"WinRPM Package: ") 216 | println(io," Name: ", names(pkg)) 217 | println(io," Summary: ", LibExpat.string_value(pkg["summary"][1])) 218 | ver = pkg["version"][1] 219 | println(io," Version: ", ver.attr["ver"], " (rel ", ver.attr["rel"], ")") 220 | println(io," Arch: ", LibExpat.string_value(pkg["arch"][1])) 221 | println(io," URL: ", LibExpat.string_value(pkg["url"][1])) 222 | println(io," License: ", LibExpat.string_value(pkg["format/rpm:license"][1])) 223 | println(io," Description: ", replace(LibExpat.string_value(pkg["description"][1]), r"\r\n|\r|\n", "\n ")) 224 | end 225 | 226 | function show(io::IO, pkgs::Packages) 227 | println(io, "WinRPM Package Set:") 228 | if isempty(pkgs) 229 | println(io, " ") 230 | else 231 | for (i,pkg) = enumerate(pkgs) 232 | name = names(pkg) 233 | summary = LibExpat.string_value(pkg["summary"][1]) 234 | arch = LibExpat.string_value(pkg["arch"][1]) 235 | println(io," $i. $name ($arch) - $summary") 236 | end 237 | end 238 | end 239 | 240 | names(pkg::Package) = LibExpat.string_value(pkg["name"][1]) 241 | names(pkgs::Packages) = [names(pkg) for pkg in pkgs] 242 | 243 | function select(pkgs::Packages, pkg::AbstractString) 244 | if length(pkgs) == 0 245 | error("Package candidate for $pkg not found") 246 | elseif length(pkgs) == 1 247 | pkg = pkgs[1] 248 | else 249 | info("Multiple package candidates found for $pkg, picking newest.") 250 | epochs = [getepoch(pkg) for pkg in pkgs] 251 | pkgs = pkgs[findin(epochs,maximum(epochs))] 252 | if length(pkgs) > 1 253 | versions = [convert(RPMVersionNumber, pkg[xpath"version/@ver"][1]) for pkg in pkgs] 254 | pkgs = pkgs[versions .== maximum(versions)] 255 | if length(pkgs) > 1 256 | release = [convert(VersionNumber, pkg[xpath"version/@rel"][1]) for pkg in pkgs] 257 | pkgs = pkgs[release .== maximum(release)] 258 | if length(pkgs) > 1 259 | warn("Multiple package candidates have the same version, picking one at random") 260 | end 261 | end 262 | end 263 | pkg = pkgs[1] 264 | end 265 | pkg 266 | end 267 | 268 | lookup(name::AbstractString, arch::AbstractString=OS_ARCH) = 269 | Packages(xpath".[name='$name']['$arch'='' or arch='$arch']") 270 | 271 | search(x::AbstractString, arch::AbstractString=OS_ARCH) = 272 | Packages(xpath".[contains(name,'$x') or contains(summary,'$x') or contains(description,'$x')]['$arch'='' or arch='$arch']") 273 | 274 | whatprovides(file::AbstractString, arch::AbstractString=OS_ARCH) = 275 | Packages(xpath".[format/file[contains(text(),'$file')]]['$arch'='' or arch='$arch']") 276 | 277 | rpm_provides(requires::AbstractString) = 278 | Packages(xpath".[format/rpm:provides/rpm:entry[@name='$requires']]") 279 | 280 | function rpm_provides{T<:AbstractString}(requires::Union{Vector{T},Set{T}}) 281 | pkgs = Set{ETree}() 282 | for x in requires 283 | pkgs_ = rpm_provides(x) 284 | if isempty(pkgs_) 285 | warn("Package not found that provides $x") 286 | else 287 | push!(pkgs, select(pkgs_,x).pd) 288 | end 289 | end 290 | Packages(pkgs) 291 | end 292 | 293 | rpm_requires(x::Package) = x[xpath"format/rpm:requires/rpm:entry/@name"] 294 | 295 | function rpm_requires(xs::Union{Vector{Package},Set{Package},Packages}) 296 | requires = Set{AbstractString}() 297 | for x in xs 298 | union!(requires, rpm_requires(x)) 299 | end 300 | requires 301 | end 302 | 303 | function rpm_url(pkg::Package) 304 | baseurl = pkg[xpath"/@url"][1] 305 | arch = pkg[xpath"string(arch)"][1] 306 | href = pkg[xpath"location/@href"][1] 307 | url = baseurl, href 308 | end 309 | 310 | function rpm_ver(pkg::Union{Package,ETree}) 311 | return (pkg[xpath"version/@ver"][1], 312 | pkg[xpath"version/@rel"][1], 313 | pkg[xpath"version/@epoch"][1]) 314 | end 315 | 316 | type RPMVersionNumber 317 | s::AbstractString 318 | end 319 | Base.convert(::Type{RPMVersionNumber}, s::AbstractString) = RPMVersionNumber(s) 320 | @compat Base.:(<)(a::RPMVersionNumber, b::RPMVersionNumber) = false 321 | @compat Base.:(==)(a::RPMVersionNumber, b::RPMVersionNumber) = true 322 | @compat Base.:(<=)(a::RPMVersionNumber, b::RPMVersionNumber) = (a == b) || (a < b) 323 | @compat Base.:(>)(a::RPMVersionNumber, b::RPMVersionNumber) = !(a <= b) 324 | @compat Base.:(>=)(a::RPMVersionNumber, b::RPMVersionNumber) = !(a < b) 325 | @compat Base.:(!=)(a::RPMVersionNumber, b::RPMVersionNumber) = !(a == b) 326 | 327 | function getepoch(pkg::Package) 328 | epoch = pkg[xpath"version/@epoch"] 329 | if isempty(epoch) 330 | 0 331 | else 332 | parse(Int, epoch[1]) 333 | end 334 | end 335 | 336 | deps(pkg::AbstractString, arch::AbstractString=OS_ARCH) = deps(select(lookup(pkg, arch), pkg)) 337 | function deps(pkg::Union{Package,Packages}) 338 | add = rpm_provides(rpm_requires(pkg)) 339 | local packages::Vector{ETree} 340 | reqd = AbstractString[] 341 | if isa(pkg, Packages) 342 | packages = ETree[p for p in pkg.p] 343 | else 344 | packages = ETree[pkg.pd] 345 | end 346 | packages = union(packages, add.p) 347 | while !isempty(add) 348 | reqs = setdiff(rpm_requires(add), reqd) 349 | append!(reqd, reqs) 350 | add = Packages(setdiff(rpm_provides(reqs).p, packages)) 351 | for p in add 352 | push!(packages, p.pd) 353 | end 354 | end 355 | return Packages(packages) 356 | end 357 | 358 | install(pkg::AbstractString, arch::AbstractString=OS_ARCH; yes=false) = install(select(lookup(pkg, arch), pkg); yes=yes) 359 | 360 | function install{T<:AbstractString}(pkgs::Vector{T}, arch::AbstractString=OS_ARCH; yes=false) 361 | todo = Package[] 362 | for pkg in pkgs 363 | push!(todo, select(lookup(pkg, arch), pkg)) 364 | end 365 | install(Packages(todo); yes=yes) 366 | end 367 | 368 | function install(pkg::Union{Package,Packages}; yes=false) 369 | todo, toup = prepare_install(pkg) 370 | if isempty(todo) && isempty(toup) 371 | info("Nothing to do") 372 | else 373 | if !isempty(toup) 374 | info("Packages to update: ", join(names(toup), ", ")) 375 | yesold = yes || prompt_ok("Continue with updates") 376 | else 377 | yesold = false 378 | end 379 | if !isempty(todo) 380 | info("Packages to install: ", join(names(todo), ", ")) 381 | yesnew = yes || prompt_ok("Continue with install") 382 | else 383 | yesnew = false 384 | end 385 | if yesold 386 | do_install(toup) 387 | end 388 | if yesnew 389 | do_install(todo) 390 | end 391 | info("Complete") 392 | end 393 | end 394 | 395 | function prepare_install(pkg::Union{Package,Packages}) 396 | packages = deps(pkg).p 397 | open(installedlist, isfile(installedlist)?"r+":"w+") do installed 398 | seek(installed, 0) 399 | installed_list = Vector{AbstractString}[] 400 | for line in eachline(installed) 401 | ln = split(chomp(line), ' ', limit=2) 402 | if length(ln) == 2 403 | push!(installed_list, ln) 404 | end 405 | end 406 | toupdate = ETree[] 407 | filter!(packages) do p 408 | ver = replace(join(rpm_ver(p), ','), r"\s", "") 409 | oldver = false 410 | for entry in p[xpath"format/rpm:provides/rpm:entry[@name]"] 411 | provides = entry.attr["name"] 412 | maybe_oldver = false 413 | anyver = false 414 | for installed in installed_list 415 | if installed[2] == provides 416 | anyver = true 417 | if ver == installed[1] 418 | maybe_oldver = false 419 | break 420 | end 421 | maybe_oldver = true 422 | end 423 | end 424 | if !anyver 425 | return true 426 | end 427 | oldver |= maybe_oldver 428 | end 429 | if oldver 430 | push!(toupdate, p) 431 | end 432 | return false 433 | end 434 | toup = Packages(reverse!(toupdate)) 435 | todo = Packages(reverse!(packages)) 436 | return todo, toup 437 | end 438 | end 439 | 440 | function do_install(packages::Packages) 441 | for package in packages 442 | do_install(package) 443 | end 444 | end 445 | 446 | function do_install(package::Package) 447 | name = names(package) 448 | source, path = rpm_url(package) 449 | info("Downloading: ", name) 450 | data = download("$source/$path") 451 | if data[2] != 200 452 | info("try running WinRPM.update() and retrying the install") 453 | error("failed to download $name $(data[2]) from $source/$path.") 454 | end 455 | cache = getcachedir(source) 456 | path2 = joinpath(cache,escape(path)) 457 | open(path2, "w") do f 458 | write(f, data[1]) 459 | end 460 | info("Extracting: ", name) 461 | cpio = splitext(path2)[1]*".cpio" 462 | local err = nothing 463 | for cmd = [`7z x -y $path2 -o$cache`, `7z x -y $cpio -o$installdir`] 464 | (out, pc) = open(cmd, "r") 465 | stdoutstr = readstring(out) 466 | if !success(pc) 467 | wait_close(out) 468 | println(stdoutstr) 469 | err = pc 470 | if is_unix() 471 | cd(installdir) do 472 | if success(`rpm2cpio $path2` | `cpio -imud`) 473 | err = nothing 474 | end 475 | end 476 | end 477 | isfile(cpio) && rm(cpio) 478 | err !== nothing && pipeline_error(err) 479 | break 480 | end 481 | end 482 | isfile(cpio) && rm(cpio) 483 | ver = replace(join(rpm_ver(package), ','), r"\s", "") 484 | open(installedlist, isfile(installedlist)?"r+":"w+") do installed 485 | for entry in package[xpath"format/rpm:provides/rpm:entry[@name]"] 486 | provides = entry.attr["name"] 487 | seekend(installed) 488 | println(installed, ver, ' ', provides) 489 | end 490 | flush(installed) 491 | end 492 | end 493 | 494 | function prompt_ok(question) 495 | while true 496 | print(question) 497 | print(" [y/N]? ") 498 | ans = strip(readline(STDIN)) 499 | if isempty(ans) || ans[1] == 'n' || ans[1] == 'N' 500 | return false 501 | elseif ans[1] == 'y' || ans[1] == 'Y' 502 | return true 503 | end 504 | println("Please answer Y or N") 505 | end 506 | end 507 | 508 | function help() 509 | less(joinpath(dirname(dirname(@__FILE__)), "README.md")) 510 | end 511 | 512 | include("winrpm_bindeps.jl") 513 | 514 | end 515 | --------------------------------------------------------------------------------