|
| 1 | +#!/usr/bin/env julia |
| 2 | + |
| 3 | +import Pkg |
| 4 | +import Pkg.Types: VersionSpec, VersionRange, VersionBound, semver_spec |
| 5 | +import Base: thismajor, thisminor, thispatch, nextmajor, nextminor, nextpatch |
| 6 | + |
| 7 | +const STDLIBS = [ |
| 8 | + "Base64" |
| 9 | + "CRC32c" |
| 10 | + "Dates" |
| 11 | + "DelimitedFiles" |
| 12 | + "Distributed" |
| 13 | + "FileWatching" |
| 14 | + "Future" |
| 15 | + "InteractiveUtils" |
| 16 | + "Libdl" |
| 17 | + "LibGit2" |
| 18 | + "LinearAlgebra" |
| 19 | + "Logging" |
| 20 | + "Markdown" |
| 21 | + "Mmap" |
| 22 | + "Pkg" |
| 23 | + "Printf" |
| 24 | + "Profile" |
| 25 | + "Random" |
| 26 | + "REPL" |
| 27 | + "Serialization" |
| 28 | + "SHA" |
| 29 | + "SharedArrays" |
| 30 | + "Sockets" |
| 31 | + "SparseArrays" |
| 32 | + "Statistics" |
| 33 | + "SuiteSparse" |
| 34 | + "Test" |
| 35 | + "Unicode" |
| 36 | + "UUIDs" |
| 37 | +] |
| 38 | + |
| 39 | +function uuid(name::AbstractString) |
| 40 | + if name == "Pkg" |
| 41 | + return "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" |
| 42 | + elseif name == "Statistics" |
| 43 | + return "10745b16-79ce-11e8-11f9-7d13ad32a3b2" |
| 44 | + else |
| 45 | + string(Pkg.METADATA_compatible_uuid(String(name))) |
| 46 | + end |
| 47 | +end |
| 48 | + |
| 49 | +function uses(repo::AbstractString, lib::AbstractString) |
| 50 | + pattern = string(raw"\b(import|using)\s+((\w|\.)+\s*,\s*)*", lib, raw"\b") |
| 51 | + success(`git -C $repo grep -Eq $pattern -- '*.jl'`) |
| 52 | +end |
| 53 | + |
| 54 | +function semver(intervals) |
| 55 | + spec = String[] |
| 56 | + for ival in intervals |
| 57 | + if ival.upper == v"∞" |
| 58 | + push!(spec, "≥ $(thispatch(ival.lower))") |
| 59 | + else |
| 60 | + lo, hi = ival.lower, ival.upper |
| 61 | + if lo.major < hi.major |
| 62 | + push!(spec, "^$(lo.major).$(lo.minor).$(lo.patch)") |
| 63 | + for major = lo.major+1:hi.major-1 |
| 64 | + push!(spec, "~$major") |
| 65 | + end |
| 66 | + for minor = 0:hi.minor-1 |
| 67 | + push!(spec, "~$(hi.major).$minor") |
| 68 | + end |
| 69 | + for patch = 0:hi.patch-1 |
| 70 | + push!(spec, "=$(hi.major).$(hi.minor).$patch") |
| 71 | + end |
| 72 | + elseif lo.minor < hi.minor |
| 73 | + push!(spec, "~$(lo.major).$(lo.minor).$(lo.patch)") |
| 74 | + for minor = lo.minor+1:hi.minor-1 |
| 75 | + push!(spec, "~$(hi.major).$minor") |
| 76 | + end |
| 77 | + for patch = 0:hi.patch-1 |
| 78 | + push!(spec, "=$(hi.major).$(hi.minor).$patch") |
| 79 | + end |
| 80 | + else |
| 81 | + for patch = lo.patch:hi.patch-1 |
| 82 | + push!(spec, "=$(hi.major).$(hi.minor).$patch") |
| 83 | + end |
| 84 | + end |
| 85 | + end |
| 86 | + end |
| 87 | + return join(spec, ", ") |
| 88 | +end |
| 89 | + |
| 90 | +if !isempty(ARGS) && ARGS[1] == "-f" |
| 91 | + const force = true |
| 92 | + popfirst!(ARGS) |
| 93 | +else |
| 94 | + const force = false |
| 95 | +end |
| 96 | +isempty(ARGS) && (push!(ARGS, pwd())) |
| 97 | + |
| 98 | +for arg in ARGS |
| 99 | + dir = abspath(expanduser(arg)) |
| 100 | + isdir(dir) || |
| 101 | + error("$arg does not appear to be a package (not a directory)") |
| 102 | + |
| 103 | + name = basename(dir) |
| 104 | + if isempty(name) |
| 105 | + dir = dirname(dir) |
| 106 | + name = basename(dir) |
| 107 | + end |
| 108 | + endswith(name, ".jl") && (name = chop(name, tail=3)) |
| 109 | + |
| 110 | + project_file = joinpath(dir, "Project.toml") |
| 111 | + !force && isfile(project_file) && |
| 112 | + error("$arg already has a project file") |
| 113 | + |
| 114 | + require_file = joinpath(dir, "REQUIRE") |
| 115 | + isfile(require_file) || |
| 116 | + error("$arg does not appear to be a package (no REQUIRE file)") |
| 117 | + |
| 118 | + project = Dict( |
| 119 | + "name" => name, |
| 120 | + "uuid" => uuid(name), |
| 121 | + "deps" => Dict{String,String}(), |
| 122 | + "compat" => Dict{String,String}(), |
| 123 | + "extras" => Dict{String,String}(), |
| 124 | + ) |
| 125 | + |
| 126 | + test_require_file = joinpath(dir, "test", "REQUIRE") |
| 127 | + |
| 128 | + for (file, section) in ((require_file, "deps"), |
| 129 | + (test_require_file, "extras")) |
| 130 | + isfile(file) || continue |
| 131 | + reqs = Pkg.Pkg2.Reqs.read(file) |
| 132 | + for req in reqs |
| 133 | + req isa Pkg.Pkg2.Reqs.Requirement || continue |
| 134 | + dep = String(req.package) |
| 135 | + if dep != "julia" |
| 136 | + project[section][dep] = uuid(dep) |
| 137 | + end |
| 138 | + if req.versions != Pkg.Pkg2.Pkg2Types.VersionSet() |
| 139 | + project["compat"][dep] = semver(req.versions.intervals) |
| 140 | + end |
| 141 | + end |
| 142 | + end |
| 143 | + |
| 144 | + for (srcdir, section) in (("src" => "deps"), ("test", "extras")) |
| 145 | + for stdlib in STDLIBS |
| 146 | + if uses(joinpath(dir, srcdir), stdlib) |
| 147 | + project[section][stdlib] = uuid(stdlib) |
| 148 | + end |
| 149 | + end |
| 150 | + end |
| 151 | + |
| 152 | + if !isempty(project["extras"]) |
| 153 | + project["targets"] = Dict("test" => collect(keys(project["extras"]))) |
| 154 | + haskey(project["deps"], "Test") && delete!(project["deps"], "Test") |
| 155 | + end |
| 156 | + |
| 157 | + println(stderr, "Generating project file for $name: $project_file") |
| 158 | + open(project_file, "w") do io |
| 159 | + Pkg.TOML.print(io, project, sorted=true) |
| 160 | + end |
| 161 | + project = Pkg.Types.read_project(project_file) |
| 162 | + Pkg.Types.write_project(project, project_file) |
| 163 | +end |
| 164 | + |
0 commit comments