diff --git a/Artifacts.toml b/Artifacts.toml new file mode 100644 index 000000000..aa1a80e05 --- /dev/null +++ b/Artifacts.toml @@ -0,0 +1,15 @@ +[alpine] +git-tree-sha1 = "562768a40e93d27b79fbedf9cfa7883409d494ea" +lazy = true + + [[alpine.download]] + sha256 = "5a588162779446d8e5235bf6fc97588d1e197f44cf64e3a1f88ae828270456a7" + url = "https://github.com/alpinelinux/docker-alpine/raw/2f3c3015951938c521e752899a92ecd618e0c05b/x86_64/alpine-minirootfs-3.12.4-x86_64.tar.gz" + +[debian] +git-tree-sha1 = "629416ae3d28494fea097fedc57d0fdd748731e3" +lazy = true + + [[debian.download]] + sha256 = "3c3c78a1b15490bfdc29cfd10a72f5f16195ccf64ffc4cdfb445cad387ea5b50" + url = "https://github.com/JuliaCI/PkgEval.jl/releases/download/v0.1/debian-buster-20210420.tar.xz" diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 000000000..b9de17b4d --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,523 @@ +# This file is machine-generated - editing it directly is not advised + +[[ArgParse]] +deps = ["Logging", "TextWrap"] +git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d" +uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +version = "1.1.4" + +[[ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" + +[[Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[AssetRegistry]] +deps = ["Distributed", "JSON", "Pidfile", "SHA", "Test"] +git-tree-sha1 = "b25e88db7944f98789130d7b503276bc34bc098e" +uuid = "bf4720bc-e11a-5d0c-854e-bdca1663c893" +version = "0.1.0" + +[[AutoHashEquals]] +git-tree-sha1 = "45bb6705d93be619b81451bb2006b7ee5d4e4453" +uuid = "15f4f7f2-30c1-5605-9d31-71845cf9641f" +version = "0.2.0" + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[BinaryBuilder]] +deps = ["ArgParse", "BinaryBuilderBase", "Dates", "Downloads", "GitHub", "HTTP", "JLD2", "JSON", "LibGit2", "Libdl", "Logging", "LoggingExtras", "ObjectFile", "OutputCollectors", "Pkg", "PkgLicenses", "ProgressMeter", "REPL", "Random", "Registrator", "RegistryTools", "SHA", "Scratch", "Sockets", "UUIDs", "ghr_jll"] +git-tree-sha1 = "f11b0c236a1491514ff875dec7ccff0dce80c5c4" +uuid = "12aac903-9f7c-5d81-afc2-d9565ea332ae" +version = "0.3.1" + +[[BinaryBuilderBase]] +deps = ["CodecZlib", "Downloads", "InteractiveUtils", "JSON", "LibGit2", "Libdl", "Logging", "OutputCollectors", "Pkg", "Random", "SHA", "Scratch", "SimpleBufferStream", "TOML", "Tar", "UUIDs", "p7zip_jll", "pigz_jll"] +git-tree-sha1 = "e2df852d52af02ca0d41c08730dacc8a74382ce0" +uuid = "7f725544-6523-48cd-82d1-3fa08ff4056e" +version = "0.5.4" + +[[CategoricalArrays]] +deps = ["DataAPI", "Future", "JSON", "Missings", "Printf", "Statistics", "StructTypes", "Unicode"] +git-tree-sha1 = "2ac27f59196a68070e132b25713f9a5bbc5fa0d2" +uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597" +version = "0.8.3" + +[[CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.0" + +[[Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "ac4132ad78082518ec2037ae5770b6e796f7f956" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "3.27.0" + +[[DataAPI]] +git-tree-sha1 = "dfb3b7e89e395be1e25c2ad6d7690dc29cc53b1d" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.6.0" + +[[DataFrames]] +deps = ["CategoricalArrays", "Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "Missings", "PooledArrays", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "ecd850f3d2b815431104252575e7307256121548" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "0.21.8" + +[[DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.9" + +[[DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[Downloads]] +deps = ["ArgTools", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" + +[[ExprTools]] +git-tree-sha1 = "10407a39b87f29d47ebaca8edbc75d7c302ff93e" +uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" +version = "0.1.3" + +[[FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[GitForge]] +deps = ["Dates", "HTTP", "JSON2"] +git-tree-sha1 = "ded1b4ce9518e7568bad8dc20171a34684e8c6d2" +uuid = "8f6bce27-0656-5410-875b-07a5572985df" +version = "0.1.6" + +[[GitHub]] +deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal"] +git-tree-sha1 = "a4f61fc1b1724e6eec1d9333eac2d4b01d8fcc8f" +uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26" +version = "5.4.0" + +[[HTTP]] +deps = ["Base64", "Dates", "IniFile", "MbedTLS", "Sockets"] +git-tree-sha1 = "c7ec02c4c6a039a98a15f955462cd7aea5df4508" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "0.8.19" + +[[Hiccup]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd" +uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7" +version = "0.2.2" + +[[IniFile]] +deps = ["Test"] +git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8" +uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" +version = "0.5.0" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[InvertedIndices]] +deps = ["Test"] +git-tree-sha1 = "15732c475062348b0165684ffe28e85ea8396afc" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.0.0" + +[[IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[JLD2]] +deps = ["CodecZlib", "DataStructures", "MacroTools", "Mmap", "Pkg", "Printf", "Requires", "UUIDs"] +git-tree-sha1 = "9f2f2f24e60305feb6ae293a617ddf06f429efc3" +uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +version = "0.4.3" + +[[JLLWrappers]] +deps = ["Preferences"] +git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.3.0" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.1" + +[[JSON2]] +deps = ["Dates", "Parsers", "Test"] +git-tree-sha1 = "66397cc6c08922f98a28ab05a8d3002f9853b129" +uuid = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3" +version = "0.3.2" + +[[LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" + +[[LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" + +[[LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" + +[[LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[LinearAlgebra]] +deps = ["Libdl"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "59b45fd91b743dff047313bb7af0f84167aef80d" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "0.4.6" + +[[MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.6" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] +git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.0.3" + +[[MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" + +[[Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "f8c673ccc215eb50fcadb285f522420e29e69e1c" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "0.4.5" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Mocking]] +deps = ["ExprTools"] +git-tree-sha1 = "916b850daad0d46b8c71f65f719c49957e9513ed" +uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" +version = "0.7.1" + +[[MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" + +[[Mustache]] +deps = ["Printf", "Tables"] +git-tree-sha1 = "36995ef0d532fe08119d70b2365b7b03d4e00f48" +uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" +version = "1.0.10" + +[[Mux]] +deps = ["AssetRegistry", "Base64", "HTTP", "Hiccup", "Pkg", "Sockets", "WebSockets"] +git-tree-sha1 = "2578b3cd03e4f568f213c7d51b2118f9e81c2617" +uuid = "a975b10e-0019-58db-a62f-e48ff68538c9" +version = "0.7.5" + +[[NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" + +[[ObjectFile]] +deps = ["Reexport", "StructIO", "Test"] +git-tree-sha1 = "e009c49f99dac98cb79f93b26c259ebca66eff26" +uuid = "d8793406-e978-5875-9003-1fc021f44a92" +version = "0.3.6" + +[[OrderedCollections]] +git-tree-sha1 = "4fa2ba51070ec13fcc7517db714445b4ab986bdf" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.4.0" + +[[OutputCollectors]] +git-tree-sha1 = "d86c19b7fa8ad6a4dc8ec2c726642cc6291b2941" +uuid = "6c11c7d4-943b-4e2b-80de-f2cfc2930a8c" +version = "0.1.0" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "1.1.0" + +[[Pidfile]] +deps = ["FileWatching", "Test"] +git-tree-sha1 = "1be8660b2064893cd2dae4bd004b589278e4440d" +uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307" +version = "1.2.0" + +[[Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" + +[[PkgLicenses]] +deps = ["Test"] +git-tree-sha1 = "0af826be249c6751a3e783c07b8cd3034f508943" +uuid = "fc669557-7ec9-5e45-bca9-462afbc28879" +version = "0.2.0" + +[[PooledArrays]] +deps = ["DataAPI"] +git-tree-sha1 = "b1333d4eced1826e15adbdf01a4ecaccca9d353c" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "0.5.3" + +[[Preferences]] +deps = ["TOML"] +git-tree-sha1 = "ea79e4c9077208cd3bc5d29631a26bc0cff78902" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.2.1" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "6e9c89cba09f6ef134b00e10625590746ba1e036" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.5.0" + +[[REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[Reexport]] +deps = ["Pkg"] +git-tree-sha1 = "7b1d07f411bc8ddb7977ec7f377b97b158514fe0" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "0.2.0" + +[[Registrator]] +deps = ["AutoHashEquals", "Base64", "Dates", "Distributed", "FileWatching", "GitForge", "GitHub", "HTTP", "JSON", "LibGit2", "Logging", "MbedTLS", "Mocking", "Mustache", "Mux", "Pkg", "RegistryTools", "Serialization", "Sockets", "TimeToLive", "UUIDs", "ZMQ"] +git-tree-sha1 = "fc49368213d6dcbacdc444120efa4babcf248dcc" +uuid = "4418983a-e44d-11e8-3aec-9789530b3b3e" +version = "1.2.3" + +[[RegistryTools]] +deps = ["AutoHashEquals", "LibGit2", "Pkg", "UUIDs"] +git-tree-sha1 = "60522125b10a3b71b07677302c3aedd0b4363835" +uuid = "d1eb7eb1-105f-429d-abf5-b0f65cb9e2c4" +version = "1.5.3" + +[[Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.1.3" + +[[SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[Sandbox]] +deps = ["JLLWrappers", "LazyArtifacts", "Libdl", "Preferences", "Random", "SHA", "Scratch", "TOML", "Tar", "UserNSSandbox_jll"] +git-tree-sha1 = "186536ef1adeaec028abf5d16aca3e7eed77f846" +repo-rev = "main" +repo-url = "https://github.com/staticfloat/Sandbox.jl" +uuid = "9307e30f-c43e-9ca7-d17c-c2dc59df670d" +version = "0.1.0" + +[[Scratch]] +deps = ["Dates"] +git-tree-sha1 = "ad4b278adb62d185bbcb6864dc24959ab0627bf6" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.0.3" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[SimpleBufferStream]] +git-tree-sha1 = "874e8867b33a00e784c8a7e4b60afe9e037b74e1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.1.0" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[SodiumSeal]] +deps = ["Base64", "Libdl", "libsodium_jll"] +git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99" +uuid = "2133526b-2bfb-4018-ac12-889fb3908a75" +version = "0.1.1" + +[[SortingAlgorithms]] +deps = ["DataStructures", "Random", "Test"] +git-tree-sha1 = "03f5898c9959f8115e30bc7226ada7d0df554ddd" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "0.3.1" + +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[StructIO]] +deps = ["Test"] +git-tree-sha1 = "010dc73c7146869c042b49adcdb6bf528c12e859" +uuid = "53d494c1-5632-5724-8f4c-31dff12d585f" +version = "0.3.0" + +[[StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "ad4558dee74c5d26ab0d0324766b1a3ee6ae777a" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.7.1" + +[[TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" + +[[TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"] +git-tree-sha1 = "c9d2d262e9a327be1f35844df25fe4561d258dc9" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.4.2" + +[[Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" + +[[Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[TextWrap]] +git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" +uuid = "b718987f-49a8-5099-9789-dcd902bef87d" +version = "1.0.1" + +[[TimeToLive]] +deps = ["Dates"] +git-tree-sha1 = "1f1389007d16385ec02e497bef6c2caffba99b65" +uuid = "37f0c46e-897f-50ef-b453-b26c3eed3d6c" +version = "0.3.0" + +[[TranscodingStreams]] +deps = ["Random", "Test"] +git-tree-sha1 = "7c53c35547de1c5b9d46a4797cf6d8253807108c" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.9.5" + +[[UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[UserNSSandbox_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "3bcd179912ca5295e50132f7294eaf3e22f259b4" +uuid = "b88861f7-1d72-59dd-91e7-a8cc876a4984" +version = "2021.4.20+0" + +[[WebSockets]] +deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"] +git-tree-sha1 = "f91a602e25fe6b89afc93cf02a4ae18ee9384ce3" +uuid = "104b5d7c-a370-577a-8038-80a2059c5097" +version = "1.5.9" + +[[ZMQ]] +deps = ["FileWatching", "Sockets", "ZeroMQ_jll"] +git-tree-sha1 = "fc68e8a3719166950a0f3e390a14c7302c48f8de" +uuid = "c2297ded-f4af-51ae-bb23-16f91089e4e1" +version = "1.2.1" + +[[ZeroMQ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "libsodium_jll"] +git-tree-sha1 = "74a74a3896b63980734cc876da8a103454559fe8" +uuid = "8f1865be-045e-5c20-9c9f-bfbfb0764568" +version = "4.3.2+6" + +[[Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" + +[[ghr_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "f5c8cb306d4fe2d1fff90443a088fc5ba536c134" +uuid = "07c12ed4-43bc-5495-8a2a-d5838ef8d533" +version = "0.13.0+1" + +[[libsodium_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "848ab3d00fe39d6fbc2a8641048f8f272af1c51e" +uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" +version = "1.0.20+0" + +[[nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" + +[[p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" + +[[pigz_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "8c379a72c82099ceb4be53f4f427690376279052" +uuid = "1bc43ea1-30af-5bc8-a9d4-c018457e6e3e" +version = "2.5.0+0" diff --git a/Project.toml b/Project.toml index 3ab176877..621495f4b 100644 --- a/Project.toml +++ b/Project.toml @@ -8,15 +8,13 @@ BinaryBuilder = "12aac903-9f7c-5d81-afc2-d9565ea332ae" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" -Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" -Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +Sandbox = "9307e30f-c43e-9ca7-d17c-c2dc59df670d" [compat] BinaryBuilder = "0.3" diff --git a/README.md b/README.md index 1475f2c14..ae95f4f16 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,8 @@ Testing Example tests passed Other `run` methods, that offer more options and control over the testing process, are available as well. These methods however require you to first prepare the environment -yourself, by calling `prepare_registry` to set-up the package registry, `prepare_runner` to -build the Docker image, and `prepare_julia` to download and unpack a binary version of -Julia. +yourself, by calling `prepare_registry` to set-up the package registry, and `prepare_julia` +to download and unpack a binary version of Julia. ## Why does my package fail? @@ -69,7 +68,6 @@ julia> using PkgEval julia> julia_version = v"1.3.0" # use `obtain_julia` if you need a specific build julia> julia_install = PkgEval.prepare_julia(julia_version) -julia> PkgEval.prepare_runner() julia> PkgEval.prepare_registry() julia> PkgEval.run_sandboxed_julia(julia_install) diff --git a/rootfs/README.md b/rootfs/README.md new file mode 100644 index 000000000..e2173240a --- /dev/null +++ b/rootfs/README.md @@ -0,0 +1,19 @@ +# Root filesystems + +Scripts to generate root filesystems for PkgEval containers. These should be minimal, as +packages are generally expected to install the dependencies they need themselves, using +artifacts. + +## Uploading + +The filesystem tarballs are currently attached to releases of the PkgEval.jl package itself. + +## Recording + +```julia +using ArtifactUtils + +add_artifact!("Artifacts.toml", "debian", + "https://github.com/JuliaCI/PkgEval.jl/releases..."; + force=true, lazy=true) +``` diff --git a/rootfs/debian.sh b/rootfs/debian.sh new file mode 100755 index 000000000..0bc90f3de --- /dev/null +++ b/rootfs/debian.sh @@ -0,0 +1,50 @@ +#!/bin/bash -uxe + +version="buster" +date=$(date +%Y%m%d) + +rootfs=$(mktemp --directory --tmpdir="/tmp") + +packages=() + +# download engines +packages+=(curl ca-certificates) +# essential tools +packages+=(git unzip) +# toolchain +packages+=(build-essential gfortran pkg-config) + +function join_by { local IFS="$1"; shift; echo "$*"; } +package_list=$(join_by , ${packages[@]}) + +sudo debootstrap --variant=minbase \ + --include=$package_list \ + $version "$rootfs" + +# Clean some files +sudo chroot "$rootfs" apt-get clean +sudo rm -rf "$rootfs"/var/lib/apt/lists/* + +# Remove special `dev` files +sudo rm -rf "$rootfs"/dev/* + +# Remove `_apt` user so that `apt` doesn't try to `setgroups()` +sudo sed '/_apt:/d' -i "$rootfs"/etc/passwd + +sudo chown "$(id -u)":"$(id -g)" -R "$rootfs" +pushd "$rootfs" + +# replace hardlinks with softlinks (working around JuliaIO/Tar.jl#101) +target_inode=-1 +find . -type f -links +1 -printf "%i %p\n" | sort -nk1 | while read inode path; do + if [[ $target_inode != $inode ]]; then + target_inode=$inode + target_path=$path + else + ln -sf $target_path $path + fi +done + +tar -cJf "/tmp/debian-$version-$date.tar.xz" . +popd +rm -rf "$rootfs" diff --git a/runner/Dockerfile.arch b/runner/Dockerfile.arch deleted file mode 100644 index cc727d5b5..000000000 --- a/runner/Dockerfile.arch +++ /dev/null @@ -1,18 +0,0 @@ -FROM archlinux:base-devel - -RUN pacman -Suy --noconfirm --needed \ - # download engines - curl ca-certificates \ - # essential tools - git unzip sudo \ - # toolchain - gcc-fortran \ - # Any package that needs a display (e.g. Gtk.jl) - xorg-server-xvfb \ - # clean-up - && find /var/cache/pacman/ -type f -delete - -RUN mkdir /storage /cache - -COPY ./entrypoint.sh / -ENTRYPOINT ["/entrypoint.sh"] diff --git a/runner/Dockerfile.ubuntu b/runner/Dockerfile.ubuntu deleted file mode 100644 index 1aeb776f1..000000000 --- a/runner/Dockerfile.ubuntu +++ /dev/null @@ -1,20 +0,0 @@ -FROM ubuntu:20.04 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install --no-install-recommends -y \ - # download engines - curl ca-certificates \ - # essential tools - git unzip sudo \ - # toolchain - build-essential gfortran pkg-config \ - # Any package that needs a display (e.g. Gtk.jl) - xvfb \ - # clean-up - && rm -rf /var/lib/apt/lists/* - -RUN mkdir /storage /cache - -COPY ./entrypoint.sh / -ENTRYPOINT ["/entrypoint.sh"] diff --git a/runner/entrypoint.sh b/runner/entrypoint.sh deleted file mode 100755 index 0538bec0a..000000000 --- a/runner/entrypoint.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -ue - - -# prepare the user - -USER=$1 -USER_ID=$2 -GROUP=$3 -GROUP_ID=$4 -shift 4 - -groupadd --gid $GROUP_ID $GROUP -echo "$GROUP ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers - -useradd --uid $USER_ID --gid $GROUP_ID --shell /bin/bash --no-create-home --no-user-group $USER -# manual home creation because it might be mounted tmpfs already -mkdir -p /home/$USER -chown $USER:$GROUP /home/$USER - -# make the storage and cache writable, in case we didn't mount one -chown $USER:$GROUP /storage /cache - - -# prepare the depot - -mkdir /home/$USER/.julia -chown $USER:$GROUP /home/$USER/.julia - -mkdir -p /storage/artifacts -chown $USER:$GROUP /storage/artifacts -ln -s /storage/artifacts /home/$USER/.julia/artifacts - -mkdir -p /cache/registries -chown $USER:$GROUP /cache/registries -ln -s /cache/registries /home/$USER/.julia/registries - - -# run the command - -# discover libraries (which may be mounted at run time, e.g., libcuda by the Docker runtime) -ldconfig - -cd /home/$USER -sudo --user $USER --set-home \ - CI=true PKGEVAL=true JULIA_PKGEVAL=true \ - JULIA_PKG_PRECOMPILE_AUTO=0 \ - PYTHON="" R_HOME="*" \ - -- "$@" diff --git a/src/PkgEval.jl b/src/PkgEval.jl index b611255c3..91cb96d11 100644 --- a/src/PkgEval.jl +++ b/src/PkgEval.jl @@ -7,10 +7,8 @@ using Dates using ProgressMeter using DataFrames using Random -using Libdl -using Sockets -using JSON -using Printf +using Sandbox +using LazyArtifacts # immutable: in package directory versions_file() = joinpath(dirname(@__DIR__), "deps", "Versions.toml") diff --git a/src/report.jl b/src/report.jl index be5ea1d6a..30d6fc747 100644 --- a/src/report.jl +++ b/src/report.jl @@ -48,40 +48,44 @@ function print_status(io::IO, status, val=status) end end -function compare(result, config_against, config) +function compare(result; rev::Bool=false) pkg_names = unique(result.name) - builds = result[result[!, :config] .== config, :] - reference = result[result[!, :config] .== config_against, :] + primary, against = groupby(result, [:julia]) + if rev # hack + primary, against = against, primary + end # overview - o = count(==(:ok), builds[!, :status]) - s = count(==(:skip), builds[!, :status]) - f = count(==(:fail), builds[!, :status]) - k = count(==(:kill), builds[!, :status]) - x = o + s + k + f - nrow(builds) - @assert x == nrow(builds) - print("On v$config, out of $x packages ") - print_status(:ok, o) - print(" passed, ") - print_status(:fail, f) - print(" failed, ") - print_status(:kill, k) - print(" got killed and ") - print_status(:skip, s) - println(" were skipped.") + for df in (primary, against) + o = count(==(:ok), df[!, :status]) + s = count(==(:skip), df[!, :status]) + f = count(==(:fail), df[!, :status]) + k = count(==(:kill), df[!, :status]) + x = o + s + k + f + @assert x == nrow(df) + + print("On v$(first(df.julia)), out of $x packages ") + print_status(:ok, o) + print(" passed, ") + print_status(:fail, f) + print(" failed, ") + print_status(:kill, k) + print(" got killed and ") + print_status(:skip, s) + println(" were skipped.") + end println() - # summary of differences - println("Comparing against $(config_against):") + # list of differences + println("Comparing $(first(primary.julia)) against $(first(against.julia)):") new_failures = 0 new_successes = 0 - for current in eachrow(builds) + for current in eachrow(primary) pkg_name = current[:name] - previous = reference[reference[!, :name] .== pkg_name, :] + previous = against[against[!, :name] .== pkg_name, :] nrow(previous) == 0 && continue previous = first(previous) @@ -99,6 +103,10 @@ function compare(result, config_against, config) end end end + + println() + + # summary of differences print("In summary, ") print_status(:ok, new_successes) print(" packages now succeed, while ") diff --git a/src/run.jl b/src/run.jl index 82db6a60e..ce5a50fc7 100644 --- a/src/run.jl +++ b/src/run.jl @@ -1,133 +1,188 @@ export Configuration -uid() = ccall(:getuid, Cint, ()) -gid() = ccall(:getgid, Cint, ()) - -function prepare_runner() - cd(joinpath(dirname(@__DIR__), "runner")) do - for runner in ("ubuntu", "arch") - cmd = `docker build --tag newpkgeval:$runner --file Dockerfile.$runner .` - if !isdebug(:docker) - cmd = pipeline(cmd, stdout=devnull, stderr=devnull) +lazy_artifact(x) = @artifact_str(x) + +const rootfs_lock = ReentrantLock() +const rootfs_cache = Dict() +function prepare_rootfs(distro="debian"; uid=1000, user="pkgeval", gid=1000, group="pkgeval", home="/home/$user") + lock(rootfs_lock) do + get!(rootfs_cache, (distro, uid, user, gid, group, home)) do + base = lazy_artifact(distro) + + # a bare rootfs isn't usable out-of-the-box + derived = mktempdir() + cp(base, derived; force=true) + + # add a user and group + chmod(joinpath(derived, "etc/passwd"), 0o644) + open(joinpath(derived, "etc/passwd"), "a") do io + println(io, "$user:x:$uid:$gid::$home:/bin/bash") end - Base.run(cmd) + chmod(joinpath(derived, "etc/group"), 0o644) + open(joinpath(derived, "etc/group"), "a") do io + println(io, "$group:x:$gid:") + end + chmod(joinpath(derived, "etc/shadow"), 0o640) + open(joinpath(derived, "etc/shadow"), "a") do io + println(io, "$user:*:::::::") + end + + # replace resolv.conf + rm(joinpath(derived, "etc/resolv.conf"); force=true) + write(joinpath(derived, "etc/resolv.conf"), read("/etc/resolv.conf")) + + return (path=derived, uid, user, gid, group, home) end end - - return end """ - run_sandboxed_julia(install::String, args=``; wait=true, interactive=true, - stdin=stdin, stdout=stdout, stderr=stderr, - cache=nothing, storage=nothing, kwargs...) - -Run Julia inside of a sandbox, passing the given arguments `args` to it. The argument -`install` specifies the directory where Julia can be found. + run_sandboxed_julia(install::String, args=``; env=Dict(), mounts=Dict(), + wait=true, stdin=stdin, stdout=stdout, stderr=stderr, + install_dir="/opt/julia", kwargs...) -The argument `wait` determines if the process will be waited on, and defaults to true. If -setting this argument to `false`, remember that the sandbox is using on Docker and killing -the process does not necessarily kill the container. It is advised to use the `name` keyword -argument to set the container name, and use that to kill the Julia process. +Run Julia inside of a sandbox, passing the given arguments `args` to it. The argument `wait` +determines if the process will be waited on. Streams can be connected using the `stdin`, +`stdout` and `sterr` arguments. Returns a `Process` object. -The `cache` directory is used to cache temporary files across runs, e.g. compilation caches, -and can be expected to be removed at any point. The `storage` directory can be used for more -lasting files, e.g. artifacts. - -The keyword argument `interactive` maps to the Docker option, and defaults to true. +Further customization is possible using the `env` arg, to set environment variables, and the +`mounts` argument to mount additional directories. With `install_dir`, the directory where +Julia is installed can be chosen. """ function run_sandboxed_julia(install::String, args=``; wait=true, - stdin=stdin, stdout=stdout, stderr=stderr, kwargs...) - cmd = runner_sandboxed_julia(install, args; kwargs...) - Base.run(pipeline(cmd, stdin=stdin, stdout=stdout, stderr=stderr); wait=wait) -end - -function runner_sandboxed_julia(install::String, args=``; interactive=true, tty=true, - name=nothing, cpus::Vector{Int}=Int[], tmpfs::Bool=true, - storage=nothing, cache=nothing, sysimage=nothing, - xvfb::Bool=true, init::Bool=true, - runner="ubuntu", user="pkgeval", group="pkgeval", - install_dir="/opt/julia") - ## Docker args - - cmd = `docker run --rm` - - # expose any available GPUs if they are available - if find_library("libcuda") != "" - cmd = `$cmd --gpus all -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=all` - end + mounts::Dict{String,String}=Dict{String,String}(), + kwargs...) + config, cmd = runner_sandboxed_julia(install, args; kwargs...) - # mount data - julia_path = installed_julia_dir(install) - @assert isdir(julia_path) - cmd = ```$cmd --mount type=bind,source=$julia_path,target=$install_dir,readonly - --env JULIA_DEPOT_PATH="::/usr/local/share/julia" - --env JULIA_PKG_SERVER - ``` - - if storage !== nothing - cmd = `$cmd --mount type=bind,source=$storage,target=/storage` - end + # XXX: even when preferred_executor() returns UnprivilegedUserNamespacesExecutor, + # sometimes a stray sudo happens at run time? no idea how. + exe_typ = UnprivilegedUserNamespacesExecutor + exe = exe_typ() + proc = Base.run(exe, config, cmd; wait) - if cache !== nothing - cmd = `$cmd --mount type=bind,source=$cache,target=/cache` - end + # TODO: introduce a --stats flag that has the sandbox trace and report on CPU, network, ... usage - # mount working directory in tmpfs - if tmpfs - cmd = `$cmd --tmpfs /home/$user:exec` + if wait + cleanup(exe) + else + @async begin + try + Base.wait(proc) + cleanup(exe) + catch err + @error "Unexpected error while cleaning up process" exception=(err, catch_backtrace()) + end + end end - # restrict resource usage - if !isempty(cpus) - cmd = `$cmd --cpuset-cpus=$(join(cpus, ','))` - end + return proc +end - if interactive - cmd = `$cmd --interactive` - end +# global Xvfb process for use by all containers +const xvfb_lock = ReentrantLock() +const xvfb_proc = Ref{Union{Base.Process,Nothing}}(nothing) - if tty - cmd = `$cmd --tty` +function runner_sandboxed_julia(install::String, args=``; install_dir="/opt/julia", + stdin=stdin, stdout=stdout, stderr=stderr, + env::Dict{String,String}=Dict{String,String}(), + mounts::Dict{String,String}=Dict{String,String}(), + xvfb::Bool=true, cpus::Vector{Int}=Int[]) + julia_path = installed_julia_dir(install) + rootfs = prepare_rootfs() + read_only_maps = Dict( + "/" => rootfs.path, + install_dir => julia_path, + "/usr/local/share/julia/registries" => registry_dir(), + ) + + artifacts_path = joinpath(storage_dir(), "artifacts") + mkpath(artifacts_path) + read_write_maps = merge(mounts, Dict( + joinpath(rootfs.home, ".julia/artifacts") => artifacts_path + )) + + env = merge(env, Dict( + # PkgEval detection + "CI" => "true", + "PKGEVAL" => "true", + "JULIA_PKGEVAL" => "true", + + # use the provided registry + # NOTE: putting a registry in a non-primary depot entry makes Pkg use it as-is, + # without needingb to set Pkg.UPDATED_REGISTRY_THIS_SESSION. + "JULIA_DEPOT_PATH" => "::/usr/local/share/julia", + + # some essential env vars (since we don't run from a shell) + "PATH" => "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin", + "HOME" => rootfs.home, + )) + if haskey(ENV, "TERM") + env["TERM"] = ENV["TERM"] end - if name !== nothing - cmd = `$cmd --name $name` - end + if xvfb + lock(xvfb_lock) do + if xvfb_proc[] === nothing || !process_running(xvfb_proc[]) + proc = Base.run(`Xvfb :1 -screen 0 1024x768x16`; wait=false) + sleep(1) + process_running(proc) || error("Could not start Xvfb") + + xvfb_proc[] === nothing && atexit() do + kill(xvfb_proc[]) + wait(xvfb_proc[]) + end + xvfb_proc[] = proc + end + end - if init - cmd = `$cmd --init` + env["DISPLAY"] = ":1" + read_write_maps["/tmp/.X11-unix"] = "/tmp/.X11-unix" end - cmd = `$cmd newpkgeval:$runner` - - - ## Entrypoint script args + cmd = `$install_dir/bin/julia` - # use the current user and group ID to ensure cache and storage are writable - container_uid = uid() - container_gid = gid() - if container_uid < 1000 || container_gid < 1000 - # system ids might conflict with groups/users in the container - # TODO: can we use userns-remap? - @warn """"You are running PkgEval as a system user (with id $uid:$gid); this is not compatible with the container set-up. - I will be using id 1000:1000, but that means the cache and storage on the host file system will not be owned by you.""" + # restrict resource usage + if !isempty(cpus) + cmd = `/usr/bin/taskset --cpu-list $(join(cpus, ',')) $cmd` + env["JULIA_CPU_THREADS"] = string(length(cpus)) # JuliaLang/julia#35787 end - cmd = `$cmd $user $container_uid $group $container_gid` + # NOTE: we use persist=true so that modifications to the rootfs are backed by + # actual storage on the host, and not just the (1G hard-coded) tmpfs, + # because some packages like to generate a lot of data during testing. + config = SandboxConfig(read_only_maps, read_write_maps, env; + rootfs.uid, rootfs.gid, pwd=rootfs.home, persist=true, + stdin, stdout, stderr, verbose=isdebug(:sandbox)) - ## Julia args + return config, `$cmd $args` +end - if sysimage !== nothing - args = `--sysimage=$sysimage $args` +function process_children(pid) + pids = Int[] + for tid in readdir("/proc/$pid/task") + children = read("/proc/$pid/task/$tid/children", String) + append!(pids, parse.(Int, split(children))) end + pids +end - if xvfb - `$cmd xvfb-run $install_dir/bin/julia $args` - else - `$cmd $install_dir/bin/julia $args` - end +function cpu_time(pid) + stats = read("/proc/$pid/stat", String) + m = match(r"^(\d+) \((.+)\) (.+)", stats) + @assert m !== nothing + fields = [[m.captures[1], m.captures[2]]; split(m.captures[3])] + utime = parse(Int, fields[14]) + stime = parse(Int, fields[15]) + cutime = parse(Int, fields[16]) + cstime = parse(Int, fields[17]) + total_time = (utime + stime + cutime + cstime) / Sys.SC_CLK_TCK + + # cutime and cstime are only updated when the child exits, + # so recursively scan all known children + total_time += sum(cpu_time, process_children(pid); init=0.0) + + return total_time end """ @@ -149,7 +204,6 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, time_limit = 60*60, do_depwarns=false, kwargs...) # prepare for launching a container - container = "$(pkg.name)-$(randstring(8))" script = raw""" try using Dates @@ -159,12 +213,10 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, versioninfo() println() - using Pkg - Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true - print("\n\n", '#'^80, "\n# Installation: $(now())\n#\n\n") + using Pkg Pkg.add(ARGS[1]) @@ -180,9 +232,6 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, println() finally print("\n\n", '#'^80, "\n# PkgEval teardown: $(now())\n#\n\n") - - # give the host some time to pick the above marker up, and kill us - sleep(60) end""" cmd = do_depwarns ? `--depwarn=error` : `` cmd = `$cmd --eval 'eval(Meta.parse(read(stdin,String)))' $(pkg.name)` @@ -190,15 +239,19 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, input = Pipe() output = Pipe() - function stop() - kill_container(container) + env = Dict( + "JULIA_PKG_PRECOMPILE_AUTO" => "0", + # package hacks + "PYTHON" => "", + "R_HOME" => "*" + ) + if haskey(ENV, "JULIA_PKG_SERVER") + env["JULIA_PKG_SERVER"] = ENV["JULIA_PKG_SERVER"] end - container_lock = ReentrantLock() - - p = run_sandboxed_julia(install, cmd; name=container, tty=false, wait=false, - stdout=output, stderr=output, stdin=input, - kwargs...) + proc = run_sandboxed_julia(install, cmd; env, wait=false, + stdout=output, stderr=output, stdin=input, + kwargs...) close(output.in) # pass the script over standard input to avoid exceeding max command line size, @@ -206,118 +259,84 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, println(input, script) close(input) + function stop() + if process_running(proc) + # FIXME: if we only kill proc, we sometimes only end up killing the sandbox. + # shouldn't the sandbox handle this, e.g., by creating a process group? + function recursive_kill(proc, sig) + parent_pid = getpid(proc) + for pid in reverse([parent_pid; process_children(parent_pid)]) + ccall(:uv_kill, Cint, (Cint, Cint), pid, Base.SIGKILL) + end + return + end + + recursive_kill(proc, Base.SIGINT) + terminator = Timer(5) do timer + recursive_kill(proc, Base.SIGTERM) + end + killer = Timer(10) do timer + recursive_kill(proc, Base.SIGKILL) + end + wait(proc) + close(terminator) + close(killer) + end + close(output) + end + status = nothing reason = missing # kill on timeout - t = Timer(time_limit) do timer - lock(container_lock) do - process_running(p) || return - status = :kill - reason = :time_limit - stop() - end + timeout_monitor = Timer(time_limit) do timer + process_running(proc) || return + status = :kill + reason = :time_limit + stop() end - # kill on inactivity (fewer than 1 second of CPU usage every minute) - previous_stats = nothing - t2 = Timer(60; interval=300) do timer - lock(container_lock) do - process_running(p) || return - try - current_stats = query_container(container) - if previous_stats !== nothing && previous_stats["cpu_stats"] !== nothing && - current_stats !== nothing && current_stats["cpu_stats"] !== nothing - previous_cpu_stats = previous_stats["cpu_stats"] - current_cpu_stats = current_stats["cpu_stats"] - usage_diff = (current_cpu_stats["cpu_usage"]["total_usage"] - - previous_cpu_stats["cpu_usage"]["total_usage"]) / 1e9 - if 0 <= usage_diff < 1 - status = :kill - reason = :inactivity - stop() - end - end - previous_stats = current_stats - catch err - @error "Failed to check for inactivity" exception=(err, catch_backtrace()) + # kill on inactivity (less than 1 second of CPU usage every minute) + previous_cpu_time = nothing + inactivity_monitor = Timer(6; interval=30) do timer + process_running(proc) || return + pid = getpid(proc) + current_cpu_time = cpu_time(pid) + if current_cpu_time > 0 && previous_cpu_time !== nothing + cpu_time_diff = current_cpu_time - previous_cpu_time + if 0 <= cpu_time_diff < 1 + status = :kill + reason = :inactivity + stop() end end + previous_cpu_time = current_cpu_time end - # collect output and stats - t3 = @async begin + # collect output + log_monitor = @async begin io = IOBuffer() - stats = nothing - while process_running(p) && isopen(output) - line = readline(output) - println(io, line) - - # right before the container ends, gather some statistics - if occursin("PkgEval teardown", line) - lock(container_lock) do - process_running(p) || return - stats = query_container(container) - stop() - end - break - end + while isopen(output) + write(io, output) # kill on too-large logs if io.size > log_limit - lock(container_lock) do - process_running(p) || return - status = :kill - reason = :log_limit - close(output) - stop() - end + process_running(proc) || break + status = :kill + reason = :log_limit + stop() break end end - write(io, output) # finish copying remaining output from kernel buffer - return String(take!(io)), stats + return String(take!(io)) end - wait(p) - close(t) - close(t2) - log, stats = fetch(t3) + wait(proc) + close(timeout_monitor) + close(inactivity_monitor) + log = fetch(log_monitor) @assert !isopen(output) && eof(output) - # append some simple statistics to the log - # TODO: serialize the statistics - if stats !== nothing - io = IOBuffer() - - try - cpu_stats = stats["cpu_stats"] - @printf(io, "CPU usage: %.2fs (%.2fs user, %.2fs kernel)\n", - cpu_stats["cpu_usage"]["total_usage"]/1e9, - cpu_stats["cpu_usage"]["usage_in_usermode"]/1e9, - cpu_stats["cpu_usage"]["usage_in_kernelmode"]/1e9) - println(io) - - println(io, "Network usage:") - for (network, network_stats) in stats["networks"] - println(io, "- $network: $(Base.format_bytes(network_stats["rx_bytes"])) received, $(Base.format_bytes(network_stats["tx_bytes"])) sent") - end - catch err - print(io, "Could not render usage statistics: ") - Base.showerror(io, err) - Base.show_backtrace(io, catch_backtrace()) - println(io) - end - - println(io) - print(io, "Raw statistics: ") - JSON.print(io, stats) - - log *= String(take!(io)) - else - log *= "No statistics gathered." - end - # pick up the installed package version from the log version_match = match(Regex("Installed $(pkg.name) .+ v(.+)"), log) version = if version_match !== nothing @@ -332,7 +351,7 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, end # try to figure out the failure reason - if status == nothing + if status === nothing if occursin("PkgEval succeeded", log) status = :ok else @@ -371,27 +390,6 @@ function run_sandboxed_test(install::String, pkg; log_limit = 2^20 #= 1 MB =#, return version, status, reason, log end -function query_container(container) - docker = connect("/var/run/docker.sock") - write(docker, """GET /containers/$container/stats HTTP/1.1 - Host: localhost""") - write(docker, "\r\n\r\n") - headers = readuntil(docker, "\r\n\r\n") - occursin("HTTP/1.1 200 OK", headers) || return nothing - len = parse(Int, readuntil(docker, "\r\n"); base=16) - len > 0 || return nothing - body = String(read(docker, len)) - stats = JSON.parse(body) - close(docker) - return stats -end - -function kill_container(container) - if !success(pipeline(`docker stop $container`, stdout=devnull)) - @warn "Could not stop container $container" - end -end - Base.@kwdef struct Configuration julia::VersionNumber = Base.VERSION # TODO: depwarn, checkbounds, etc @@ -405,40 +403,11 @@ function run(configs::Vector{Configuration}, pkgs::Vector; ninstances::Integer=Sys.CPU_THREADS, retries::Integer=2, kwargs...) # here we deal with managing execution: spawning workers, output, result I/O, etc - prepare_runner() - - # Julia installation and local cache - instantiated_configs = Dict{Configuration,Tuple{String,String}}() + # Julia installation + instantiated_configs = Dict{Configuration,String}() for config in configs install = prepare_julia(config.julia) - cache = mktempdir() - - # copy the registry (we can't mount it because permissions might be incompatible) - # XXX: actually have the target Julia process check-out the registry? - registry_path = registry_dir() - @assert isdir(registry_path) - cp(registry_path, joinpath(cache, "registries")) - - instantiated_configs[config] = (install, cache) - end - - # global storage - storage = storage_dir() - mkpath(storage) - - # ensure we can use Docker's API - info = let - docker = connect("/var/run/docker.sock") - write(docker, - """GET /info HTTP/1.1 - Host: localhost""") - write(docker, "\r\n\r\n") - headers = readuntil(docker, "\r\n\r\n") - occursin("HTTP/1.1 200 OK", headers) || error("Invalid reply: $headers") - len = parse(Int, readuntil(docker, "\r\n"); base=16) - body = String(read(docker, len)) - close(docker) - JSON.parse(body) + instantiated_configs[config] = install end jobs = vec(collect(Iterators.product(instantiated_configs, pkgs))) @@ -471,7 +440,6 @@ function run(configs::Vector{Configuration}, pkgs::Vector; start = now() io = IOContext(IOBuffer(), :color=>true) - on_ci = parse(Bool, get(ENV, "CI", "false")) p = Progress(njobs; barlen=50, color=:normal) function update_output() # known statuses @@ -492,7 +460,7 @@ function run(configs::Vector{Configuration}, pkgs::Vector; end end - if on_ci + if !isinteractive() println("$x combinations to test ($o succeeded, $f failed, $k killed, $s skipped, $(runtimestr(start)))") sleep(10) else @@ -528,7 +496,8 @@ function run(configs::Vector{Configuration}, pkgs::Vector; end end - result = DataFrame(config = Configuration[], + # NOTE: we expand the Configuration into separate columns + result = DataFrame(julia = VersionNumber[], name = String[], uuid = UUID[], version = Union{Missing,VersionNumber}[], @@ -556,7 +525,7 @@ function run(configs::Vector{Configuration}, pkgs::Vector; push!(all_workers, @async begin try while !isempty(jobs) && !done - (config, (install, cache)), pkg = pop!(jobs) + (config, install), pkg = pop!(jobs) times[i] = now() running[i] = (config, pkg) @@ -584,26 +553,28 @@ function run(configs::Vector{Configuration}, pkgs::Vector; supported = any(values(julia_supported)) end if !supported - push!(result, [config, + push!(result, [config.julia, pkg.name, pkg.uuid, missing, :skip, :unsupported, 0, missing]) + running[i] = nothing continue elseif pkg.name in skip_lists[pkg.registry] - push!(result, [config, + push!(result, [config.julia, pkg.name, pkg.uuid, missing, :skip, :explicit, 0, missing]) + running[i] = nothing continue elseif endswith(pkg.name, "_jll") - push!(result, [config, + push!(result, [config.julia, pkg.name, pkg.uuid, missing, :skip, :jll, 0, missing]) + running[i] = nothing continue end # perform an initial run pkg_version, status, reason, log = - run_sandboxed_test(install, pkg; cache=cache, - storage=storage, cpus=[i-1], kwargs...) + run_sandboxed_test(install, pkg; cpus=[i-1], kwargs...) # certain packages are known to have flaky tests; retry them for j in 1:retries @@ -611,13 +582,12 @@ function run(configs::Vector{Configuration}, pkgs::Vector; pkg.name in retry_lists[pkg.registry] times[i] = now() pkg_version, status, reason, log = - run_sandboxed_test(install, pkg; cache=cache, - storage=storage, cpus=[i-1], kwargs...) + run_sandboxed_test(install, pkg; cpus=[i-1], kwargs...) end end duration = (now()-times[i]) / Millisecond(1000) - push!(result, [config, + push!(result, [config.julia, pkg.name, pkg.uuid, pkg_version, status, reason, duration, log]) running[i] = nothing @@ -636,13 +606,8 @@ function run(configs::Vector{Configuration}, pkgs::Vector; println() # clean-up - for (config, (install,cache)) in instantiated_configs + for (config, install) in instantiated_configs rm(install; recursive=true) - if uid() < 1000 || gid() < 1000 - @warn "Cannot remove cache due to running as system user or group" - else - rm(cache; recursive=true) - end end end @@ -673,3 +638,4 @@ end run(julia_versions::Vector{VersionNumber}, args...; kwargs...) = run([Configuration(julia=julia_version) for julia_version in julia_versions], args...; kwargs...) +prepare_runner() = return diff --git a/test/runtests.jl b/test/runtests.jl index 1848397fb..efbe11e92 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,11 +9,9 @@ const julia_version = PkgEval.obtain_julia(julia_spec)::VersionNumber const julia_install = PkgEval.prepare_julia(julia_version) @testset "sandbox" begin - PkgEval.prepare_runner() mktemp() do path, io try - PkgEval.run_sandboxed_julia(julia_install, `-e 'print(1337)'`; stdout=io, - tty=false, interactive=false) + PkgEval.run_sandboxed_julia(julia_install, `-e 'print(1337)'`; stdout=io) close(io) @test read(path, String) == "1337" catch @@ -25,8 +23,7 @@ const julia_install = PkgEval.prepare_julia(julia_version) end # print versioninfo so we can verify in CI logs that the correct version is used - PkgEval.run_sandboxed_julia(julia_install, `-e 'using InteractiveUtils; versioninfo()'`; - tty=false, interactive=false) + PkgEval.run_sandboxed_julia(julia_install, `-e 'using InteractiveUtils; versioninfo()'`) end const pkgnames = ["TimerOutputs", "Crayons", "Example", "Gtk"] @@ -38,7 +35,7 @@ const pkgnames = ["TimerOutputs", "Crayons", "Example", "Gtk"] # timeouts results = PkgEval.run([Configuration(julia=julia_version)], pkgs; time_limit = 0.1) - @test all(results.status .== :kill) + @test all(results.status .== :kill) && all(results.reason .== :time_limit) end @testset "main entrypoint" begin @@ -55,7 +52,7 @@ end lts = Configuration(julia=v"1.0.5") stable = Configuration(julia=v"1.2.0") results = PkgEval.run([lts, stable], ["Example"]) - PkgEval.compare(results, lts, stable) + PkgEval.compare(results) end PkgEval.purge()