Skip to content

Commit

Permalink
working
Browse files Browse the repository at this point in the history
  • Loading branch information
joshday committed Jan 10, 2024
1 parent 4dcd462 commit f1f0a2b
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 68 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[compat]
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# DepotDelivery

[![Build Status](https://github.com/joshday/DepotDelivery.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/joshday/DepotDelivery.jl/actions/workflows/CI.yml?query=branch%3Amain)

**DepotDelivery** provides a mechanism of delivering standalone Julia applications into environments where Julia is already installed.

It uses Julia's fantastic Pkg/Artifacts system to "cross compile" for different platforms.


## Usage

```julia
using DepotDelivery

b = BuildSpec(path_to_project_toml; platform = Base.BinaryPlatforms.HostPlatform())

path = build(b)
```

## Why Would I Use This?

- This is really only useful for installing into air-gapped environments.
130 changes: 64 additions & 66 deletions src/DepotDelivery.jl
Original file line number Diff line number Diff line change
@@ -1,96 +1,94 @@
module DepotDelivery

using Dates, Distributed, Pkg, Scratch, Tar
using Dates, Distributed, Pkg, Scratch

export BuildSpec, build, clear!

#-----------------------------------------------------------------------------# __init__
PROJECT::String = "" # /PROJECTS/$project_root
VALIDATE::String = "" # /VALIDATE/$project_root
RELEASES::String = "" # /RELEASES/$(proj.uuid)/$platform/$predicate.tar
DEPOTS::String = "" # /DEPOTS/$(proj.uuid)/
#-----------------------------------------------------------------------------# init
BUILDS::String = ""
SANDBOX::String = ""

function __init__()
global PROJECT = @get_scratch!("PROJECT")
global VALIDATE = @get_scratch!("VALIDATE")
global RELEASES = @get_scratch!("RELEASES")
global DEPOTS = @get_scratch!("DEPOTS")
global BUILDS = get_scratch!(DepotDelivery, "BUILDS")
global SANDBOX = get_scratch!(DepotDelivery, "SANDBOX")
end

#-----------------------------------------------------------------------------# utils
clear!() = clear_scratch!(DepotDelivery)
clear!() = clear_scratchspaces!(DepotDelivery)

default_drop_patterns::Vector{String} = [".git", ".gitignore"]
mk_empty_path(path) = (rm(path; force=true, recursive=true); mkpath(path))

default_predicate(path) = !any(x -> occursin(x, path), default_drop_patterns)

#-----------------------------------------------------------------------------# BuildSpec
Base.@kwdef struct BuildSpec
project_file::String = Base.current_project()
project::Pkg.Types.Project = Pkg.Types.read_project(project_file)
platforms::Dict{String, Base.BinaryPlatforms.AbstractPlatform} = Dict("host" => Base.BinaryPlatforms.HostPlatform())
tar_predicates::Dict{String, Function} = Dict("default" => default_predicate)
function set_depot!(path::String)
push!(empty!(DEPOT_PATH), path)
ENV["JULIA_DEPOT_PATH"] = path # used by spawned worker processes (is this needed?)
end

function paths(b::BuildSpec)
uuid = b.project.uuid
out = (; depot = joinpath(DEPOTS, uuid), releases = joinpath(RELEASES, uuid))
foreach(out) do x
rm(x; force=true, recursive=true)
mkpath(x)
#-----------------------------------------------------------------------------# sandbox
function sandbox(f::Function)
path = mk_empty_path(SANDBOX)

current_project = Base.current_project()
depot_path = copy(DEPOT_PATH)
_depot_path = get(ENV, "JULIA_DEPOT_PATH", nothing)
_precomp = get(ENV, "JULIA_PKG_PRECOMPILE_AUTO", nothing)
try
cd(path) do
Pkg.activate("path")
ENV["JULIA_DEPOT_PATH"] = path
ENV["JULIA_PKG_PRECOMPILE_AUTO"] = "0"
f()
end
catch ex
rethrow(ex)
finally
Pkg.activate(current_project)
append!(empty!(DEPOT_PATH), depot_path)
isnothing(_depot_path) ? delete!(ENV, "JULIA_DEPOT_PATH") : (ENV["JULIA_DEPOT_PATH"] = _depot_path)
isnothing(_precomp) ? delete!(ENV, "JULIA_PKG_PRECOMPILE_AUTO") : (ENV["JULIA_PKG_PRECOMPILE_AUTO"] = _precomp)
end
return out
end

#-----------------------------------------------------------------------------# BuildSpec
Base.@kwdef struct BuildSpec
project_file::String = Base.current_project()
project::Pkg.Types.Project = Pkg.Types.read_project(project_file)
platform::Base.BinaryPlatforms.AbstractPlatform = Base.BinaryPlatforms.HostPlatform()
add_startup::Bool = true
end
function Base.show(io::IO, b::BuildSpec)
print(io, "BuildSpec:", join(("\n$x: $(getfield(b, x))") for x in [:project_file, :platform, :add_startup]))
end

#-----------------------------------------------------------------------------# build
function build(b::BuildSpec)
name = b.project.name
uuid = string(b.project.uuid)
depot, releases = paths(b)

# Create the output, nested dict of: [platform][predicate] => path
out = Dict{String, Dict{String, String}}(name => Dict{String, String}() for name in keys(b.platforms))

# Things that need to be restored in `finally`
_project = Base.current_project()
_depot_path = DEPOT_PATH
_precomp_auto = get(ENV, "JULIA_PKG_PRECOMPILE_AUTO", nothing)

try
# Change the depot to our empty one
push!(empty!(DEPOT_PATH), depot)
sandbox() do
project_root = mkpath(joinpath("dev", name))
set_depot!(pwd())
cp(dirname(b.project_file), project_root; force=true)
Pkg.activate(project_root)
Pkg.instantiate(; platform = b.platform)
mkdir("config")
write(joinpath("config", "buildspec.jld"), string(b))
if b.add_startup
content = """
ENV["JULIA_DEPOT_PATH"] = joinpath(@__DIR__, "..")
# Copy the entire project directory into `depot/dev/$project_name` and activate it
depot_dev = mkpath(joinpath(depot, "dev", proj.name))
cp(dirname(b.project_file), depot_dev; force=true)
Pkg.activate(depot_dev)
import Pkg, Serialization
Pkg.activate(joinpath(@__DIR__, "..", "dev", "$name"))
for (platform_name, platform) in pairs(b.platforms)
# For each platform, wipe `artifacts/` and instantiate the project
rm(joinpath(depot, "artifacts"); recursive=true, force=true)
Pkg.instantiate(; platform)
using $name
# Now that depot is populated, apply each predicate to create tarballs
for (predicate_name, predicate) in b.tar_predicates
path = joinpath(releases, platform_name, predicate_name * ".tar")
mkpath(dirname(path))
__build_spec__ = read(joinpath(@__DIR__, "..", "config", "buildspec.jld"), String)
@info "Creating tarball for platform `$platform` with predicate `$predicate_name`..."
Tar.create(predicate, depot, path; portable=true)
out[platform_name][predicate_name] = path
end
nothing
"""
write(joinpath("config", "startup.jl"), content)
end
catch ex
@warn "An error occured while building tarballs."
rethrow(ex)
finally
Pkg.activate(_project)
isnothing(_precomp_auto) || (ENV["JULIA_PKG_PRECOMPILE_AUTO"] = _precomp_auto)
append!(empty!(DEPOT_PATH), _depot_path)

build_dir = mkpath(joinpath(BUILDS, name))
cp(pwd(), joinpath(build_dir, "build_$(Dates.format(now(), "yyyymmdd-HHMMSS"))"))
end
return out
end


Expand Down
2 changes: 0 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ res = build(b, tar_predicates=Dict(
))

path = res["host"]["no_artifacts"]

Tar.extract(path, DepotDelivery.)

0 comments on commit f1f0a2b

Please sign in to comment.