├── .gitattributes ├── .github └── workflows │ └── release.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.nix ├── Cargo.toml ├── LICENSE ├── README.md ├── run.sh └── src ├── cmd.rs ├── language.rs ├── main.rs └── non_empty_vec.rs /.gitattributes: -------------------------------------------------------------------------------- 1 | Cargo.nix linguist-generated 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Build and upload release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | env: 8 | APP_NAME: code-runner 9 | ARCHIVE_NAME: code-runner_linux-x64.tar.gz 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Install latest rust toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | default: true 24 | override: true 25 | 26 | - name: Prepare upload url 27 | run: | 28 | UPLOAD_URL="$(jq -r '.release.upload_url' "$GITHUB_EVENT_PATH" | sed -e "s/{?name,label}$/?name=${ARCHIVE_NAME}/")" 29 | echo "UPLOAD_URL=$UPLOAD_URL" >> $GITHUB_ENV 30 | 31 | - name: Build application 32 | run: | 33 | cargo build --release 34 | tar -czf $ARCHIVE_NAME -C target/release $APP_NAME 35 | 36 | - name: Upload release asset 37 | uses: actions/upload-release-asset@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | upload_url: ${{ env.UPLOAD_URL }} 42 | asset_path: ${{ env.ARCHIVE_NAME }} 43 | asset_name: ${{ env.ARCHIVE_NAME }} 44 | asset_content_type: application/gzip 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /result 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "code-runner" 7 | version = "1.4.0" 8 | dependencies = [ 9 | "serde", 10 | "serde_json", 11 | ] 12 | 13 | [[package]] 14 | name = "itoa" 15 | version = "1.0.11" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 18 | 19 | [[package]] 20 | name = "memchr" 21 | version = "2.7.4" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 24 | 25 | [[package]] 26 | name = "proc-macro2" 27 | version = "1.0.86" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 30 | dependencies = [ 31 | "unicode-ident", 32 | ] 33 | 34 | [[package]] 35 | name = "quote" 36 | version = "1.0.36" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 39 | dependencies = [ 40 | "proc-macro2", 41 | ] 42 | 43 | [[package]] 44 | name = "ryu" 45 | version = "1.0.18" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 48 | 49 | [[package]] 50 | name = "serde" 51 | version = "1.0.208" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" 54 | dependencies = [ 55 | "serde_derive", 56 | ] 57 | 58 | [[package]] 59 | name = "serde_derive" 60 | version = "1.0.208" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "serde_json" 71 | version = "1.0.125" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" 74 | dependencies = [ 75 | "itoa", 76 | "memchr", 77 | "ryu", 78 | "serde", 79 | ] 80 | 81 | [[package]] 82 | name = "syn" 83 | version = "2.0.75" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" 86 | dependencies = [ 87 | "proc-macro2", 88 | "quote", 89 | "unicode-ident", 90 | ] 91 | 92 | [[package]] 93 | name = "unicode-ident" 94 | version = "1.0.12" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 97 | -------------------------------------------------------------------------------- /Cargo.nix: -------------------------------------------------------------------------------- 1 | 2 | # This file was @generated by crate2nix 0.14.0 with the command: 3 | # "generate" 4 | # See https://github.com/kolloch/crate2nix for more info. 5 | 6 | { nixpkgs ? 7 | , pkgs ? import nixpkgs { config = {}; } 8 | , lib ? pkgs.lib 9 | , stdenv ? pkgs.stdenv 10 | , buildRustCrateForPkgs ? pkgs: pkgs.buildRustCrate 11 | # This is used as the `crateOverrides` argument for `buildRustCrate`. 12 | , defaultCrateOverrides ? pkgs.defaultCrateOverrides 13 | # The features to enable for the root_crate or the workspace_members. 14 | , rootFeatures ? [ "default" ] 15 | # If true, throw errors instead of issueing deprecation warnings. 16 | , strictDeprecation ? false 17 | # Used for conditional compilation based on CPU feature detection. 18 | , targetFeatures ? [] 19 | # Whether to perform release builds: longer compile times, faster binaries. 20 | , release ? true 21 | # Additional crate2nix configuration if it exists. 22 | , crateConfig 23 | ? if builtins.pathExists ./crate-config.nix 24 | then pkgs.callPackage ./crate-config.nix {} 25 | else {} 26 | }: 27 | 28 | rec { 29 | # 30 | # "public" attributes that we attempt to keep stable with new versions of crate2nix. 31 | # 32 | 33 | rootCrate = rec { 34 | packageId = "code-runner"; 35 | 36 | # Use this attribute to refer to the derivation building your root crate package. 37 | # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }. 38 | build = internal.buildRustCrateWithFeatures { 39 | inherit packageId; 40 | }; 41 | 42 | # Debug support which might change between releases. 43 | # File a bug if you depend on any for non-debug work! 44 | debug = internal.debugCrate { inherit packageId; }; 45 | }; 46 | # Refer your crate build derivation by name here. 47 | # You can override the features with 48 | # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }. 49 | workspaceMembers = { 50 | "code-runner" = rec { 51 | packageId = "code-runner"; 52 | build = internal.buildRustCrateWithFeatures { 53 | packageId = "code-runner"; 54 | }; 55 | 56 | # Debug support which might change between releases. 57 | # File a bug if you depend on any for non-debug work! 58 | debug = internal.debugCrate { inherit packageId; }; 59 | }; 60 | }; 61 | 62 | # A derivation that joins the outputs of all workspace members together. 63 | allWorkspaceMembers = pkgs.symlinkJoin { 64 | name = "all-workspace-members"; 65 | paths = 66 | let members = builtins.attrValues workspaceMembers; 67 | in builtins.map (m: m.build) members; 68 | }; 69 | 70 | # 71 | # "internal" ("private") attributes that may change in every new version of crate2nix. 72 | # 73 | 74 | internal = rec { 75 | # Build and dependency information for crates. 76 | # Many of the fields are passed one-to-one to buildRustCrate. 77 | # 78 | # Noteworthy: 79 | # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate. 80 | # but with additional information which is used during dependency/feature resolution. 81 | # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging. 82 | # * `devDependencies` as of now not used by `buildRustCrate` but used to 83 | # inject test dependencies into the build 84 | 85 | crates = { 86 | "code-runner" = rec { 87 | crateName = "code-runner"; 88 | version = "1.4.0"; 89 | edition = "2018"; 90 | crateBin = [ 91 | { 92 | name = "code-runner"; 93 | path = "src/main.rs"; 94 | requiredFeatures = [ ]; 95 | } 96 | ]; 97 | # We can't filter paths with references in Nix 2.4 98 | # See https://github.com/NixOS/nix/issues/5410 99 | src = if ((lib.versionOlder builtins.nixVersion "2.4pre20211007") || (lib.versionOlder "2.5" builtins.nixVersion )) 100 | then lib.cleanSourceWith { filter = sourceFilter; src = ./.; } 101 | else ./.; 102 | authors = [ 103 | "Petter Rasmussen " 104 | ]; 105 | dependencies = [ 106 | { 107 | name = "serde"; 108 | packageId = "serde"; 109 | features = [ "derive" ]; 110 | } 111 | { 112 | name = "serde_json"; 113 | packageId = "serde_json"; 114 | } 115 | ]; 116 | 117 | }; 118 | "itoa" = rec { 119 | crateName = "itoa"; 120 | version = "1.0.11"; 121 | edition = "2018"; 122 | sha256 = "0nv9cqjwzr3q58qz84dcz63ggc54yhf1yqar1m858m1kfd4g3wa9"; 123 | authors = [ 124 | "David Tolnay " 125 | ]; 126 | features = { 127 | "no-panic" = [ "dep:no-panic" ]; 128 | }; 129 | }; 130 | "memchr" = rec { 131 | crateName = "memchr"; 132 | version = "2.7.4"; 133 | edition = "2021"; 134 | sha256 = "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq"; 135 | authors = [ 136 | "Andrew Gallant " 137 | "bluss" 138 | ]; 139 | features = { 140 | "compiler_builtins" = [ "dep:compiler_builtins" ]; 141 | "core" = [ "dep:core" ]; 142 | "default" = [ "std" ]; 143 | "logging" = [ "dep:log" ]; 144 | "rustc-dep-of-std" = [ "core" "compiler_builtins" ]; 145 | "std" = [ "alloc" ]; 146 | "use_std" = [ "std" ]; 147 | }; 148 | resolvedDefaultFeatures = [ "alloc" "std" ]; 149 | }; 150 | "proc-macro2" = rec { 151 | crateName = "proc-macro2"; 152 | version = "1.0.86"; 153 | edition = "2021"; 154 | sha256 = "0xrv22p8lqlfdf1w0pj4si8n2ws4aw0kilmziwf0vpv5ys6rwway"; 155 | libName = "proc_macro2"; 156 | authors = [ 157 | "David Tolnay " 158 | "Alex Crichton " 159 | ]; 160 | dependencies = [ 161 | { 162 | name = "unicode-ident"; 163 | packageId = "unicode-ident"; 164 | } 165 | ]; 166 | features = { 167 | "default" = [ "proc-macro" ]; 168 | }; 169 | resolvedDefaultFeatures = [ "proc-macro" ]; 170 | }; 171 | "quote" = rec { 172 | crateName = "quote"; 173 | version = "1.0.36"; 174 | edition = "2018"; 175 | sha256 = "19xcmh445bg6simirnnd4fvkmp6v2qiwxh5f6rw4a70h76pnm9qg"; 176 | authors = [ 177 | "David Tolnay " 178 | ]; 179 | dependencies = [ 180 | { 181 | name = "proc-macro2"; 182 | packageId = "proc-macro2"; 183 | usesDefaultFeatures = false; 184 | } 185 | ]; 186 | features = { 187 | "default" = [ "proc-macro" ]; 188 | "proc-macro" = [ "proc-macro2/proc-macro" ]; 189 | }; 190 | resolvedDefaultFeatures = [ "proc-macro" ]; 191 | }; 192 | "ryu" = rec { 193 | crateName = "ryu"; 194 | version = "1.0.18"; 195 | edition = "2018"; 196 | sha256 = "17xx2s8j1lln7iackzd9p0sv546vjq71i779gphjq923vjh5pjzk"; 197 | authors = [ 198 | "David Tolnay " 199 | ]; 200 | features = { 201 | "no-panic" = [ "dep:no-panic" ]; 202 | }; 203 | }; 204 | "serde" = rec { 205 | crateName = "serde"; 206 | version = "1.0.208"; 207 | edition = "2018"; 208 | sha256 = "1cng2zkvv6hh137jrrqdmhi2hllfnqwlqjgviqjalkv8rg98bw6g"; 209 | authors = [ 210 | "Erick Tryzelaar " 211 | "David Tolnay " 212 | ]; 213 | dependencies = [ 214 | { 215 | name = "serde_derive"; 216 | packageId = "serde_derive"; 217 | optional = true; 218 | } 219 | { 220 | name = "serde_derive"; 221 | packageId = "serde_derive"; 222 | target = { target, features }: false; 223 | } 224 | ]; 225 | devDependencies = [ 226 | { 227 | name = "serde_derive"; 228 | packageId = "serde_derive"; 229 | } 230 | ]; 231 | features = { 232 | "default" = [ "std" ]; 233 | "derive" = [ "serde_derive" ]; 234 | "serde_derive" = [ "dep:serde_derive" ]; 235 | }; 236 | resolvedDefaultFeatures = [ "default" "derive" "serde_derive" "std" ]; 237 | }; 238 | "serde_derive" = rec { 239 | crateName = "serde_derive"; 240 | version = "1.0.208"; 241 | edition = "2015"; 242 | sha256 = "1krblvy84j6d9zj12ms9l2wfbwf2w8jazkx0bf7fs4vnzy0qw014"; 243 | procMacro = true; 244 | authors = [ 245 | "Erick Tryzelaar " 246 | "David Tolnay " 247 | ]; 248 | dependencies = [ 249 | { 250 | name = "proc-macro2"; 251 | packageId = "proc-macro2"; 252 | usesDefaultFeatures = false; 253 | features = [ "proc-macro" ]; 254 | } 255 | { 256 | name = "quote"; 257 | packageId = "quote"; 258 | usesDefaultFeatures = false; 259 | features = [ "proc-macro" ]; 260 | } 261 | { 262 | name = "syn"; 263 | packageId = "syn"; 264 | usesDefaultFeatures = false; 265 | features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ]; 266 | } 267 | ]; 268 | features = { 269 | }; 270 | resolvedDefaultFeatures = [ "default" ]; 271 | }; 272 | "serde_json" = rec { 273 | crateName = "serde_json"; 274 | version = "1.0.125"; 275 | edition = "2021"; 276 | sha256 = "1v9idlv5nq5f000qsv6v02h2zbsfk2m6d05af2zgbk3kl0sygj43"; 277 | authors = [ 278 | "Erick Tryzelaar " 279 | "David Tolnay " 280 | ]; 281 | dependencies = [ 282 | { 283 | name = "itoa"; 284 | packageId = "itoa"; 285 | } 286 | { 287 | name = "memchr"; 288 | packageId = "memchr"; 289 | usesDefaultFeatures = false; 290 | } 291 | { 292 | name = "ryu"; 293 | packageId = "ryu"; 294 | } 295 | { 296 | name = "serde"; 297 | packageId = "serde"; 298 | usesDefaultFeatures = false; 299 | } 300 | ]; 301 | devDependencies = [ 302 | { 303 | name = "serde"; 304 | packageId = "serde"; 305 | features = [ "derive" ]; 306 | } 307 | ]; 308 | features = { 309 | "alloc" = [ "serde/alloc" ]; 310 | "default" = [ "std" ]; 311 | "indexmap" = [ "dep:indexmap" ]; 312 | "preserve_order" = [ "indexmap" "std" ]; 313 | "std" = [ "memchr/std" "serde/std" ]; 314 | }; 315 | resolvedDefaultFeatures = [ "default" "std" ]; 316 | }; 317 | "syn" = rec { 318 | crateName = "syn"; 319 | version = "2.0.75"; 320 | edition = "2021"; 321 | sha256 = "1nf72xc8pabwny8d0d4kjk5vmadg3fa240dywznka6gw6hq0dbzn"; 322 | authors = [ 323 | "David Tolnay " 324 | ]; 325 | dependencies = [ 326 | { 327 | name = "proc-macro2"; 328 | packageId = "proc-macro2"; 329 | usesDefaultFeatures = false; 330 | } 331 | { 332 | name = "quote"; 333 | packageId = "quote"; 334 | optional = true; 335 | usesDefaultFeatures = false; 336 | } 337 | { 338 | name = "unicode-ident"; 339 | packageId = "unicode-ident"; 340 | } 341 | ]; 342 | features = { 343 | "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ]; 344 | "printing" = [ "dep:quote" ]; 345 | "proc-macro" = [ "proc-macro2/proc-macro" "quote?/proc-macro" ]; 346 | "test" = [ "syn-test-suite/all-features" ]; 347 | }; 348 | resolvedDefaultFeatures = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ]; 349 | }; 350 | "unicode-ident" = rec { 351 | crateName = "unicode-ident"; 352 | version = "1.0.12"; 353 | edition = "2018"; 354 | sha256 = "0jzf1znfpb2gx8nr8mvmyqs1crnv79l57nxnbiszc7xf7ynbjm1k"; 355 | authors = [ 356 | "David Tolnay " 357 | ]; 358 | 359 | }; 360 | }; 361 | 362 | # 363 | # crate2nix/default.nix (excerpt start) 364 | # 365 | 366 | /* Target (platform) data for conditional dependencies. 367 | This corresponds roughly to what buildRustCrate is setting. 368 | */ 369 | makeDefaultTarget = platform: { 370 | unix = platform.isUnix; 371 | windows = platform.isWindows; 372 | fuchsia = true; 373 | test = false; 374 | 375 | /* We are choosing an arbitrary rust version to grab `lib` from, 376 | which is unfortunate, but `lib` has been version-agnostic the 377 | whole time so this is good enough for now. 378 | */ 379 | os = pkgs.rust.lib.toTargetOs platform; 380 | arch = pkgs.rust.lib.toTargetArch platform; 381 | family = pkgs.rust.lib.toTargetFamily platform; 382 | vendor = pkgs.rust.lib.toTargetVendor platform; 383 | env = "gnu"; 384 | endian = 385 | if platform.parsed.cpu.significantByte.name == "littleEndian" 386 | then "little" else "big"; 387 | pointer_width = toString platform.parsed.cpu.bits; 388 | debug_assertions = false; 389 | }; 390 | 391 | /* Filters common temp files and build files. */ 392 | # TODO(pkolloch): Substitute with gitignore filter 393 | sourceFilter = name: type: 394 | let 395 | baseName = builtins.baseNameOf (builtins.toString name); 396 | in 397 | ! ( 398 | # Filter out git 399 | baseName == ".gitignore" 400 | || (type == "directory" && baseName == ".git") 401 | 402 | # Filter out build results 403 | || ( 404 | type == "directory" && ( 405 | baseName == "target" 406 | || baseName == "_site" 407 | || baseName == ".sass-cache" 408 | || baseName == ".jekyll-metadata" 409 | || baseName == "build-artifacts" 410 | ) 411 | ) 412 | 413 | # Filter out nix-build result symlinks 414 | || ( 415 | type == "symlink" && lib.hasPrefix "result" baseName 416 | ) 417 | 418 | # Filter out IDE config 419 | || ( 420 | type == "directory" && ( 421 | baseName == ".idea" || baseName == ".vscode" 422 | ) 423 | ) || lib.hasSuffix ".iml" baseName 424 | 425 | # Filter out nix build files 426 | || baseName == "Cargo.nix" 427 | 428 | # Filter out editor backup / swap files. 429 | || lib.hasSuffix "~" baseName 430 | || builtins.match "^\\.sw[a-z]$$" baseName != null 431 | || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null 432 | || lib.hasSuffix ".tmp" baseName 433 | || lib.hasSuffix ".bak" baseName 434 | || baseName == "tests.nix" 435 | ); 436 | 437 | /* Returns a crate which depends on successful test execution 438 | of crate given as the second argument. 439 | 440 | testCrateFlags: list of flags to pass to the test exectuable 441 | testInputs: list of packages that should be available during test execution 442 | */ 443 | crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }: 444 | assert builtins.typeOf testCrateFlags == "list"; 445 | assert builtins.typeOf testInputs == "list"; 446 | assert builtins.typeOf testPreRun == "string"; 447 | assert builtins.typeOf testPostRun == "string"; 448 | let 449 | # override the `crate` so that it will build and execute tests instead of 450 | # building the actual lib and bin targets We just have to pass `--test` 451 | # to rustc and it will do the right thing. We execute the tests and copy 452 | # their log and the test executables to $out for later inspection. 453 | test = 454 | let 455 | drv = testCrate.override 456 | ( 457 | _: { 458 | buildTests = true; 459 | } 460 | ); 461 | # If the user hasn't set any pre/post commands, we don't want to 462 | # insert empty lines. This means that any existing users of crate2nix 463 | # don't get a spurious rebuild unless they set these explicitly. 464 | testCommand = pkgs.lib.concatStringsSep "\n" 465 | (pkgs.lib.filter (s: s != "") [ 466 | testPreRun 467 | "$f $testCrateFlags 2>&1 | tee -a $out" 468 | testPostRun 469 | ]); 470 | in 471 | pkgs.runCommand "run-tests-${testCrate.name}" 472 | { 473 | inherit testCrateFlags; 474 | buildInputs = testInputs; 475 | } '' 476 | set -e 477 | 478 | export RUST_BACKTRACE=1 479 | 480 | # recreate a file hierarchy as when running tests with cargo 481 | 482 | # the source for test data 483 | # It's necessary to locate the source in $NIX_BUILD_TOP/source/ 484 | # instead of $NIX_BUILD_TOP/ 485 | # because we compiled those test binaries in the former and not the latter. 486 | # So all paths will expect source tree to be there and not in the build top directly. 487 | # For example: $NIX_BUILD_TOP := /build in general, if you ask yourself. 488 | # NOTE: There could be edge cases if `crate.sourceRoot` does exist but 489 | # it's very hard to reason about them. 490 | # Open a bug if you run into this! 491 | mkdir -p source/ 492 | cd source/ 493 | 494 | ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${crate.src} 495 | 496 | # build outputs 497 | testRoot=target/debug 498 | mkdir -p $testRoot 499 | 500 | # executables of the crate 501 | # we copy to prevent std::env::current_exe() to resolve to a store location 502 | for i in ${crate}/bin/*; do 503 | cp "$i" "$testRoot" 504 | done 505 | chmod +w -R . 506 | 507 | # test harness executables are suffixed with a hash, like cargo does 508 | # this allows to prevent name collision with the main 509 | # executables of the crate 510 | hash=$(basename $out) 511 | for file in ${drv}/tests/*; do 512 | f=$testRoot/$(basename $file)-$hash 513 | cp $file $f 514 | ${testCommand} 515 | done 516 | ''; 517 | in 518 | pkgs.runCommand "${crate.name}-linked" 519 | { 520 | inherit (crate) outputs crateName; 521 | passthru = (crate.passthru or { }) // { 522 | inherit test; 523 | }; 524 | } 525 | (lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' 526 | echo tested by ${test} 527 | '' + '' 528 | ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs} 529 | ''); 530 | 531 | /* A restricted overridable version of builtRustCratesWithFeatures. */ 532 | buildRustCrateWithFeatures = 533 | { packageId 534 | , features ? rootFeatures 535 | , crateOverrides ? defaultCrateOverrides 536 | , buildRustCrateForPkgsFunc ? null 537 | , runTests ? false 538 | , testCrateFlags ? [ ] 539 | , testInputs ? [ ] 540 | # Any command to run immediatelly before a test is executed. 541 | , testPreRun ? "" 542 | # Any command run immediatelly after a test is executed. 543 | , testPostRun ? "" 544 | }: 545 | lib.makeOverridable 546 | ( 547 | { features 548 | , crateOverrides 549 | , runTests 550 | , testCrateFlags 551 | , testInputs 552 | , testPreRun 553 | , testPostRun 554 | }: 555 | let 556 | buildRustCrateForPkgsFuncOverriden = 557 | if buildRustCrateForPkgsFunc != null 558 | then buildRustCrateForPkgsFunc 559 | else 560 | ( 561 | if crateOverrides == pkgs.defaultCrateOverrides 562 | then buildRustCrateForPkgs 563 | else 564 | pkgs: (buildRustCrateForPkgs pkgs).override { 565 | defaultCrateOverrides = crateOverrides; 566 | } 567 | ); 568 | builtRustCrates = builtRustCratesWithFeatures { 569 | inherit packageId features; 570 | buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden; 571 | runTests = false; 572 | }; 573 | builtTestRustCrates = builtRustCratesWithFeatures { 574 | inherit packageId features; 575 | buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden; 576 | runTests = true; 577 | }; 578 | drv = builtRustCrates.crates.${packageId}; 579 | testDrv = builtTestRustCrates.crates.${packageId}; 580 | derivation = 581 | if runTests then 582 | crateWithTest 583 | { 584 | crate = drv; 585 | testCrate = testDrv; 586 | inherit testCrateFlags testInputs testPreRun testPostRun; 587 | } 588 | else drv; 589 | in 590 | derivation 591 | ) 592 | { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; }; 593 | 594 | /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc 595 | for the corresponding crate. 596 | */ 597 | builtRustCratesWithFeatures = 598 | { packageId 599 | , features 600 | , crateConfigs ? crates 601 | , buildRustCrateForPkgsFunc 602 | , runTests 603 | , makeTarget ? makeDefaultTarget 604 | } @ args: 605 | assert (builtins.isAttrs crateConfigs); 606 | assert (builtins.isString packageId); 607 | assert (builtins.isList features); 608 | assert (builtins.isAttrs (makeTarget stdenv.hostPlatform)); 609 | assert (builtins.isBool runTests); 610 | let 611 | rootPackageId = packageId; 612 | mergedFeatures = mergePackageFeatures 613 | ( 614 | args // { 615 | inherit rootPackageId; 616 | target = makeTarget stdenv.hostPlatform // { test = runTests; }; 617 | } 618 | ); 619 | # Memoize built packages so that reappearing packages are only built once. 620 | builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs; 621 | mkBuiltByPackageIdByPkgs = pkgs: 622 | let 623 | self = { 624 | crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs; 625 | target = makeTarget pkgs.stdenv.hostPlatform; 626 | build = mkBuiltByPackageIdByPkgs pkgs.buildPackages; 627 | }; 628 | in 629 | self; 630 | buildByPackageIdForPkgsImpl = self: pkgs: packageId: 631 | let 632 | features = mergedFeatures."${packageId}" or [ ]; 633 | crateConfig' = crateConfigs."${packageId}"; 634 | crateConfig = 635 | builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ]; 636 | devDependencies = 637 | lib.optionals 638 | (runTests && packageId == rootPackageId) 639 | (crateConfig'.devDependencies or [ ]); 640 | dependencies = 641 | dependencyDerivations { 642 | inherit features; 643 | inherit (self) target; 644 | buildByPackageId = depPackageId: 645 | # proc_macro crates must be compiled for the build architecture 646 | if crateConfigs.${depPackageId}.procMacro or false 647 | then self.build.crates.${depPackageId} 648 | else self.crates.${depPackageId}; 649 | dependencies = 650 | (crateConfig.dependencies or [ ]) 651 | ++ devDependencies; 652 | }; 653 | buildDependencies = 654 | dependencyDerivations { 655 | inherit features; 656 | inherit (self.build) target; 657 | buildByPackageId = depPackageId: 658 | self.build.crates.${depPackageId}; 659 | dependencies = crateConfig.buildDependencies or [ ]; 660 | }; 661 | dependenciesWithRenames = 662 | let 663 | buildDeps = filterEnabledDependencies { 664 | inherit features; 665 | inherit (self) target; 666 | dependencies = crateConfig.dependencies or [ ] ++ devDependencies; 667 | }; 668 | hostDeps = filterEnabledDependencies { 669 | inherit features; 670 | inherit (self.build) target; 671 | dependencies = crateConfig.buildDependencies or [ ]; 672 | }; 673 | in 674 | lib.filter (d: d ? "rename") (hostDeps ++ buildDeps); 675 | # Crate renames have the form: 676 | # 677 | # { 678 | # crate_name = [ 679 | # { version = "1.2.3"; rename = "crate_name01"; } 680 | # ]; 681 | # # ... 682 | # } 683 | crateRenames = 684 | let 685 | grouped = 686 | lib.groupBy 687 | (dependency: dependency.name) 688 | dependenciesWithRenames; 689 | versionAndRename = dep: 690 | let 691 | package = crateConfigs."${dep.packageId}"; 692 | in 693 | { inherit (dep) rename; inherit (package) version; }; 694 | in 695 | lib.mapAttrs (name: builtins.map versionAndRename) grouped; 696 | in 697 | buildRustCrateForPkgsFunc pkgs 698 | ( 699 | crateConfig // { 700 | # https://github.com/NixOS/nixpkgs/issues/218712 701 | dontStrip = stdenv.hostPlatform.isDarwin; 702 | src = crateConfig.src or ( 703 | pkgs.fetchurl rec { 704 | name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz"; 705 | # https://www.pietroalbini.org/blog/downloading-crates-io/ 706 | # Not rate-limited, CDN URL. 707 | url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate"; 708 | sha256 = 709 | assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}"); 710 | crateConfig.sha256; 711 | } 712 | ); 713 | extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}"; 714 | inherit features dependencies buildDependencies crateRenames release; 715 | } 716 | ); 717 | in 718 | builtByPackageIdByPkgs; 719 | 720 | /* Returns the actual derivations for the given dependencies. */ 721 | dependencyDerivations = 722 | { buildByPackageId 723 | , features 724 | , dependencies 725 | , target 726 | }: 727 | assert (builtins.isList features); 728 | assert (builtins.isList dependencies); 729 | assert (builtins.isAttrs target); 730 | let 731 | enabledDependencies = filterEnabledDependencies { 732 | inherit dependencies features target; 733 | }; 734 | depDerivation = dependency: buildByPackageId dependency.packageId; 735 | in 736 | map depDerivation enabledDependencies; 737 | 738 | /* Returns a sanitized version of val with all values substituted that cannot 739 | be serialized as JSON. 740 | */ 741 | sanitizeForJson = val: 742 | if builtins.isAttrs val 743 | then lib.mapAttrs (n: sanitizeForJson) val 744 | else if builtins.isList val 745 | then builtins.map sanitizeForJson val 746 | else if builtins.isFunction val 747 | then "function" 748 | else val; 749 | 750 | /* Returns various tools to debug a crate. */ 751 | debugCrate = { packageId, target ? makeDefaultTarget stdenv.hostPlatform }: 752 | assert (builtins.isString packageId); 753 | let 754 | debug = rec { 755 | # The built tree as passed to buildRustCrate. 756 | buildTree = buildRustCrateWithFeatures { 757 | buildRustCrateForPkgsFunc = _: lib.id; 758 | inherit packageId; 759 | }; 760 | sanitizedBuildTree = sanitizeForJson buildTree; 761 | dependencyTree = sanitizeForJson 762 | ( 763 | buildRustCrateWithFeatures { 764 | buildRustCrateForPkgsFunc = _: crate: { 765 | "01_crateName" = crate.crateName or false; 766 | "02_features" = crate.features or [ ]; 767 | "03_dependencies" = crate.dependencies or [ ]; 768 | }; 769 | inherit packageId; 770 | } 771 | ); 772 | mergedPackageFeatures = mergePackageFeatures { 773 | features = rootFeatures; 774 | inherit packageId target; 775 | }; 776 | diffedDefaultPackageFeatures = diffDefaultPackageFeatures { 777 | inherit packageId target; 778 | }; 779 | }; 780 | in 781 | { internal = debug; }; 782 | 783 | /* Returns differences between cargo default features and crate2nix default 784 | features. 785 | 786 | This is useful for verifying the feature resolution in crate2nix. 787 | */ 788 | diffDefaultPackageFeatures = 789 | { crateConfigs ? crates 790 | , packageId 791 | , target 792 | }: 793 | assert (builtins.isAttrs crateConfigs); 794 | let 795 | prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; }); 796 | mergedFeatures = 797 | prefixValues 798 | "crate2nix" 799 | (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; }); 800 | configs = prefixValues "cargo" crateConfigs; 801 | combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ]; 802 | onlyInCargo = 803 | builtins.attrNames 804 | (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined); 805 | onlyInCrate2Nix = 806 | builtins.attrNames 807 | (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined); 808 | differentFeatures = lib.filterAttrs 809 | ( 810 | n: v: 811 | (v ? "crate2nix") 812 | && (v ? "cargo") 813 | && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ]) 814 | ) 815 | combined; 816 | in 817 | builtins.toJSON { 818 | inherit onlyInCargo onlyInCrate2Nix differentFeatures; 819 | }; 820 | 821 | /* Returns an attrset mapping packageId to the list of enabled features. 822 | 823 | If multiple paths to a dependency enable different features, the 824 | corresponding feature sets are merged. Features in rust are additive. 825 | */ 826 | mergePackageFeatures = 827 | { crateConfigs ? crates 828 | , packageId 829 | , rootPackageId ? packageId 830 | , features ? rootFeatures 831 | , dependencyPath ? [ crates.${packageId}.crateName ] 832 | , featuresByPackageId ? { } 833 | , target 834 | # Adds devDependencies to the crate with rootPackageId. 835 | , runTests ? false 836 | , ... 837 | } @ args: 838 | assert (builtins.isAttrs crateConfigs); 839 | assert (builtins.isString packageId); 840 | assert (builtins.isString rootPackageId); 841 | assert (builtins.isList features); 842 | assert (builtins.isList dependencyPath); 843 | assert (builtins.isAttrs featuresByPackageId); 844 | assert (builtins.isAttrs target); 845 | assert (builtins.isBool runTests); 846 | let 847 | crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}"); 848 | expandedFeatures = expandFeatures (crateConfig.features or { }) features; 849 | enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures; 850 | depWithResolvedFeatures = dependency: 851 | let 852 | inherit (dependency) packageId; 853 | features = dependencyFeatures enabledFeatures dependency; 854 | in 855 | { inherit packageId features; }; 856 | resolveDependencies = cache: path: dependencies: 857 | assert (builtins.isAttrs cache); 858 | assert (builtins.isList dependencies); 859 | let 860 | enabledDependencies = filterEnabledDependencies { 861 | inherit dependencies target; 862 | features = enabledFeatures; 863 | }; 864 | directDependencies = map depWithResolvedFeatures enabledDependencies; 865 | foldOverCache = op: lib.foldl op cache directDependencies; 866 | in 867 | foldOverCache 868 | ( 869 | cache: { packageId, features }: 870 | let 871 | cacheFeatures = cache.${packageId} or [ ]; 872 | combinedFeatures = sortedUnique (cacheFeatures ++ features); 873 | in 874 | if cache ? ${packageId} && cache.${packageId} == combinedFeatures 875 | then cache 876 | else 877 | mergePackageFeatures { 878 | features = combinedFeatures; 879 | featuresByPackageId = cache; 880 | inherit crateConfigs packageId target runTests rootPackageId; 881 | } 882 | ); 883 | cacheWithSelf = 884 | let 885 | cacheFeatures = featuresByPackageId.${packageId} or [ ]; 886 | combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures); 887 | in 888 | featuresByPackageId // { 889 | "${packageId}" = combinedFeatures; 890 | }; 891 | cacheWithDependencies = 892 | resolveDependencies cacheWithSelf "dep" 893 | ( 894 | crateConfig.dependencies or [ ] 895 | ++ lib.optionals 896 | (runTests && packageId == rootPackageId) 897 | (crateConfig.devDependencies or [ ]) 898 | ); 899 | cacheWithAll = 900 | resolveDependencies 901 | cacheWithDependencies "build" 902 | (crateConfig.buildDependencies or [ ]); 903 | in 904 | cacheWithAll; 905 | 906 | /* Returns the enabled dependencies given the enabled features. */ 907 | filterEnabledDependencies = { dependencies, features, target }: 908 | assert (builtins.isList dependencies); 909 | assert (builtins.isList features); 910 | assert (builtins.isAttrs target); 911 | 912 | lib.filter 913 | ( 914 | dep: 915 | let 916 | targetFunc = dep.target or (features: true); 917 | in 918 | targetFunc { inherit features target; } 919 | && ( 920 | !(dep.optional or false) 921 | || builtins.any (doesFeatureEnableDependency dep) features 922 | ) 923 | ) 924 | dependencies; 925 | 926 | /* Returns whether the given feature should enable the given dependency. */ 927 | doesFeatureEnableDependency = dependency: feature: 928 | let 929 | name = dependency.rename or dependency.name; 930 | prefix = "${name}/"; 931 | len = builtins.stringLength prefix; 932 | startsWithPrefix = builtins.substring 0 len feature == prefix; 933 | in 934 | feature == name || feature == "dep:" + name || startsWithPrefix; 935 | 936 | /* Returns the expanded features for the given inputFeatures by applying the 937 | rules in featureMap. 938 | 939 | featureMap is an attribute set which maps feature names to lists of further 940 | feature names to enable in case this feature is selected. 941 | */ 942 | expandFeatures = featureMap: inputFeatures: 943 | assert (builtins.isAttrs featureMap); 944 | assert (builtins.isList inputFeatures); 945 | let 946 | expandFeaturesNoCycle = oldSeen: inputFeatures: 947 | if inputFeatures != [ ] 948 | then 949 | let 950 | # The feature we're currently expanding. 951 | feature = builtins.head inputFeatures; 952 | # All the features we've seen/expanded so far, including the one 953 | # we're currently processing. 954 | seen = oldSeen // { ${feature} = 1; }; 955 | # Expand the feature but be careful to not re-introduce a feature 956 | # that we've already seen: this can easily cause a cycle, see issue 957 | # #209. 958 | enables = builtins.filter (f: !(seen ? "${f}")) (featureMap."${feature}" or [ ]); 959 | in 960 | [ feature ] ++ (expandFeaturesNoCycle seen (builtins.tail inputFeatures ++ enables)) 961 | # No more features left, nothing to expand to. 962 | else [ ]; 963 | outFeatures = expandFeaturesNoCycle { } inputFeatures; 964 | in 965 | sortedUnique outFeatures; 966 | 967 | /* This function adds optional dependencies as features if they are enabled 968 | indirectly by dependency features. This function mimics Cargo's behavior 969 | described in a note at: 970 | https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features 971 | */ 972 | enableFeatures = dependencies: features: 973 | assert (builtins.isList features); 974 | assert (builtins.isList dependencies); 975 | let 976 | additionalFeatures = lib.concatMap 977 | ( 978 | dependency: 979 | assert (builtins.isAttrs dependency); 980 | let 981 | enabled = builtins.any (doesFeatureEnableDependency dependency) features; 982 | in 983 | if (dependency.optional or false) && enabled 984 | then [ (dependency.rename or dependency.name) ] 985 | else [ ] 986 | ) 987 | dependencies; 988 | in 989 | sortedUnique (features ++ additionalFeatures); 990 | 991 | /* 992 | Returns the actual features for the given dependency. 993 | 994 | features: The features of the crate that refers this dependency. 995 | */ 996 | dependencyFeatures = features: dependency: 997 | assert (builtins.isList features); 998 | assert (builtins.isAttrs dependency); 999 | let 1000 | defaultOrNil = 1001 | if dependency.usesDefaultFeatures or true 1002 | then [ "default" ] 1003 | else [ ]; 1004 | explicitFeatures = dependency.features or [ ]; 1005 | additionalDependencyFeatures = 1006 | let 1007 | name = dependency.rename or dependency.name; 1008 | stripPrefixMatch = prefix: s: 1009 | if lib.hasPrefix prefix s 1010 | then lib.removePrefix prefix s 1011 | else null; 1012 | extractFeature = feature: lib.findFirst 1013 | (f: f != null) 1014 | null 1015 | (map (prefix: stripPrefixMatch prefix feature) [ 1016 | (name + "/") 1017 | (name + "?/") 1018 | ]); 1019 | dependencyFeatures = lib.filter (f: f != null) (map extractFeature features); 1020 | in 1021 | dependencyFeatures; 1022 | in 1023 | defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures; 1024 | 1025 | /* Sorts and removes duplicates from a list of strings. */ 1026 | sortedUnique = features: 1027 | assert (builtins.isList features); 1028 | assert (builtins.all builtins.isString features); 1029 | let 1030 | outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features; 1031 | outFeaturesUnique = builtins.attrNames outFeaturesSet; 1032 | in 1033 | builtins.sort (a: b: a < b) outFeaturesUnique; 1034 | 1035 | deprecationWarning = message: value: 1036 | if strictDeprecation 1037 | then builtins.throw "strictDeprecation enabled, aborting: ${message}" 1038 | else builtins.trace message value; 1039 | 1040 | # 1041 | # crate2nix/default.nix (excerpt end) 1042 | # 1043 | }; 1044 | } 1045 | 1046 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code-runner" 3 | version = "1.4.0" 4 | authors = ["Petter Rasmussen "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | serde = { version = "1.0.116", features = ["derive"] } 11 | serde_json = "1.0.58" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Petter Rasmussen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code-runner 2 | 3 | 4 | ## Overview 5 | code-runner is a command line application that reads code as a 6 | json payload from stdin – compiles and runs the code – and writes 7 | the result as json to stdout. 8 | This is used by [glot-languages](https://github.com/glotcode/glot-languages) to run code on [glot.io](https://glot.io) 9 | See the [overview](https://github.com/glotcode/glot) on how everything is connected. 10 | 11 | 12 | ## Input (stdin) 13 | The input is required to be a json object containing the properties `runInstructions`, 14 | `files` and `stdin`. `files` must be an array with at least one object containing the 15 | properties `name` and `content`. `name` is the name of the file and can include 16 | forward slashes to create the file in a subdirectory relative to the base 17 | directory. All files are written into the same base directory under the OS's 18 | temp dir. 19 | 20 | 21 | ## Output (stdout) 22 | The output is a json object containing the properties `stdout`, `stderr` and 23 | `error`. `stdout` and `stderr` is captured from the output of the ran code. 24 | `error` is popuplated if there is a compiler / interpreter error. 25 | 26 | ## Examples 27 | 28 | ### Simple example 29 | ##### Input 30 | ```javascript 31 | { 32 | "runInstructions": { 33 | "buildCommands": [], 34 | "runCommand": "python main.py" 35 | }, 36 | "files": [ 37 | { 38 | "name": "main.py", 39 | "content": "print(42)" 40 | } 41 | ], 42 | "stdin": null 43 | } 44 | ``` 45 | 46 | ##### Output 47 | ```javascript 48 | { 49 | "stdout": "42\n", 50 | "stderr": "", 51 | "error": "" 52 | } 53 | ``` 54 | 55 | ### Read from stdin 56 | ##### Input 57 | ```javascript 58 | { 59 | "runInstructions": { 60 | "buildCommands": [], 61 | "runCommand": "python main.py" 62 | }, 63 | "files": [ 64 | { 65 | "name": "main.py", 66 | "content": "print(input('Number from stdin: '))" 67 | } 68 | ], 69 | "stdin": "42" 70 | } 71 | ``` 72 | 73 | ##### Output 74 | ```javascript 75 | { 76 | "stdout": "Number from stdin: 42\n", 77 | "stderr": "", 78 | "error": "" 79 | } 80 | ``` -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JSON=$(cat <<-END 4 | { 5 | "language": "python", 6 | "files": [ 7 | { 8 | "name": "main.py", 9 | "content": "print(42)" 10 | } 11 | ] 12 | } 13 | END) 14 | 15 | echo $JSON | cargo run 16 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::io::Write; 4 | use std::path; 5 | use std::process; 6 | use std::string; 7 | use std::time::Duration; 8 | use std::time::Instant; 9 | 10 | pub struct Options { 11 | pub work_path: path::PathBuf, 12 | pub command: String, 13 | pub stdin: Option, 14 | } 15 | 16 | pub fn run(options: Options) -> Result { 17 | let now = Instant::now(); 18 | let output = execute(options).map_err(|err| Error::Execute(err, now.elapsed()))?; 19 | let elapsed = now.elapsed(); 20 | get_output(output, elapsed).map_err(|err| Error::Output(err, now.elapsed())) 21 | } 22 | 23 | #[derive(Debug)] 24 | pub enum Error { 25 | Execute(ExecuteError, Duration), 26 | Output(OutputError, Duration), 27 | } 28 | 29 | impl Error { 30 | pub fn duration(&self) -> Duration { 31 | match self { 32 | Error::Execute(_, duration) => *duration, 33 | Error::Output(_, duration) => *duration, 34 | } 35 | } 36 | } 37 | 38 | impl fmt::Display for Error { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | match self { 41 | Error::Execute(err, _) => { 42 | write!(f, "Error while executing command. {}", err) 43 | } 44 | 45 | Error::Output(err, _) => { 46 | write!(f, "Error in output from command. {}", err) 47 | } 48 | } 49 | } 50 | } 51 | 52 | #[derive(Debug)] 53 | pub enum ExecuteError { 54 | Execute(io::Error), 55 | CaptureStdin(), 56 | WriteStdin(io::Error), 57 | WaitForChild(io::Error), 58 | } 59 | 60 | impl fmt::Display for ExecuteError { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | match self { 63 | ExecuteError::Execute(err) => { 64 | write!(f, "{}", err) 65 | } 66 | 67 | ExecuteError::CaptureStdin() => { 68 | write!(f, "Failed to capture stdin.") 69 | } 70 | 71 | ExecuteError::WriteStdin(err) => { 72 | write!(f, "Failed to write to stdin. {}", err) 73 | } 74 | 75 | ExecuteError::WaitForChild(err) => { 76 | write!(f, "Failed while waiting for child. {}", err) 77 | } 78 | } 79 | } 80 | } 81 | 82 | pub fn execute(options: Options) -> Result { 83 | let mut child = process::Command::new("sh") 84 | .arg("-c") 85 | .arg(options.command) 86 | .current_dir(&options.work_path) 87 | .stdin(process::Stdio::piped()) 88 | .stderr(process::Stdio::piped()) 89 | .stdout(process::Stdio::piped()) 90 | .spawn() 91 | .map_err(ExecuteError::Execute)?; 92 | 93 | if let Some(stdin) = options.stdin { 94 | child 95 | .stdin 96 | .as_mut() 97 | .ok_or(ExecuteError::CaptureStdin())? 98 | .write_all(stdin.as_bytes()) 99 | .map_err(ExecuteError::WriteStdin)?; 100 | } 101 | 102 | child.wait_with_output().map_err(ExecuteError::WaitForChild) 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct SuccessOutput { 107 | pub stdout: String, 108 | pub stderr: String, 109 | pub duration: Duration, 110 | } 111 | 112 | #[derive(Debug)] 113 | pub struct ErrorOutput { 114 | pub stdout: String, 115 | pub stderr: String, 116 | pub exit_code: Option, 117 | } 118 | 119 | impl fmt::Display for ErrorOutput { 120 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 121 | let mut messages = Vec::new(); 122 | 123 | if let Some(code) = self.exit_code { 124 | messages.push(format!("code: {}", code)); 125 | } 126 | 127 | if !self.stdout.is_empty() { 128 | messages.push(format!("stdout: {}", self.stdout)) 129 | } 130 | 131 | if !self.stderr.is_empty() { 132 | messages.push(format!("stderr: {}", self.stderr)) 133 | } 134 | 135 | write!(f, "{}", messages.join(", ")) 136 | } 137 | } 138 | 139 | #[derive(Debug)] 140 | pub enum OutputError { 141 | ExitFailure(ErrorOutput), 142 | ReadStdout(string::FromUtf8Error), 143 | ReadStderr(string::FromUtf8Error), 144 | } 145 | 146 | impl fmt::Display for OutputError { 147 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 148 | match self { 149 | OutputError::ExitFailure(err) => { 150 | write!(f, "Exited with non-zero exit code. {}", err) 151 | } 152 | 153 | OutputError::ReadStdout(err) => { 154 | write!(f, "Failed to read stdout. {}", err) 155 | } 156 | 157 | OutputError::ReadStderr(err) => { 158 | write!(f, "Failed to read stderr. {}", err) 159 | } 160 | } 161 | } 162 | } 163 | 164 | pub fn get_output( 165 | output: process::Output, 166 | duration: Duration, 167 | ) -> Result { 168 | if output.status.success() { 169 | let stdout = String::from_utf8(output.stdout).map_err(OutputError::ReadStdout)?; 170 | 171 | let stderr = String::from_utf8(output.stderr).map_err(OutputError::ReadStderr)?; 172 | 173 | Ok(SuccessOutput { 174 | stdout, 175 | stderr, 176 | duration, 177 | }) 178 | } else { 179 | let stdout = String::from_utf8(output.stdout).map_err(OutputError::ReadStdout)?; 180 | 181 | let stderr = String::from_utf8(output.stderr).map_err(OutputError::ReadStderr)?; 182 | 183 | let exit_code = output.status.code(); 184 | 185 | Err(OutputError::ExitFailure(ErrorOutput { 186 | stdout, 187 | stderr, 188 | exit_code, 189 | })) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/language.rs: -------------------------------------------------------------------------------- 1 | use crate::non_empty_vec; 2 | use serde::Deserialize; 3 | use std::path; 4 | 5 | #[derive(serde::Deserialize, Debug)] 6 | #[serde(rename_all = "lowercase")] 7 | pub enum Language { 8 | Assembly, 9 | Ats, 10 | Bash, 11 | C, 12 | Clisp, 13 | Clojure, 14 | Cobol, 15 | CoffeeScript, 16 | Cpp, 17 | Crystal, 18 | Csharp, 19 | D, 20 | Dart, 21 | Elixir, 22 | Elm, 23 | Erlang, 24 | Fsharp, 25 | Go, 26 | Groovy, 27 | Guile, 28 | Hare, 29 | Haskell, 30 | Idris, 31 | Java, 32 | JavaScript, 33 | Julia, 34 | Kotlin, 35 | Lua, 36 | Mercury, 37 | Nim, 38 | Nix, 39 | Ocaml, 40 | Pascal, 41 | Perl, 42 | Php, 43 | Python, 44 | Raku, 45 | Ruby, 46 | Rust, 47 | SaC, 48 | Scala, 49 | Swift, 50 | TypeScript, 51 | Zig, 52 | } 53 | 54 | #[derive(Debug, Deserialize)] 55 | #[serde(rename_all = "camelCase")] 56 | pub struct RunInstructions { 57 | pub build_commands: Vec, 58 | pub run_command: String, 59 | } 60 | 61 | pub fn run_instructions( 62 | language: &Language, 63 | files: non_empty_vec::NonEmptyVec, 64 | ) -> RunInstructions { 65 | let (main_file, other_files) = files.parts(); 66 | let main_file_str = main_file.to_string_lossy(); 67 | 68 | match language { 69 | Language::Assembly => RunInstructions { 70 | build_commands: vec![ 71 | format!("nasm -f elf64 -o a.o {}", main_file_str), 72 | "ld -o a.out a.o".to_string(), 73 | ], 74 | run_command: "./a.out".to_string(), 75 | }, 76 | 77 | Language::Ats => RunInstructions { 78 | build_commands: vec![format!( 79 | "patscc -o a.out {} {}", 80 | main_file_str, 81 | source_files(other_files, "dats") 82 | )], 83 | run_command: "./a.out".to_string(), 84 | }, 85 | 86 | Language::Bash => RunInstructions { 87 | build_commands: vec![], 88 | run_command: format!("bash {}", main_file_str), 89 | }, 90 | 91 | Language::C => RunInstructions { 92 | build_commands: vec![format!( 93 | "clang -o a.out -lm {} {}", 94 | main_file_str, 95 | source_files(other_files, "c") 96 | )], 97 | run_command: "./a.out".to_string(), 98 | }, 99 | 100 | Language::Clisp => RunInstructions { 101 | build_commands: vec![], 102 | run_command: format!("sbcl --noinform --non-interactive --load {}", main_file_str), 103 | }, 104 | 105 | Language::Clojure => RunInstructions { 106 | build_commands: vec![], 107 | run_command: format!("clj -M {}", main_file_str), 108 | }, 109 | 110 | Language::Cobol => RunInstructions { 111 | build_commands: vec![format!( 112 | "cobc -x -o a.out {} {}", 113 | main_file_str, 114 | source_files(other_files, "cob") 115 | )], 116 | run_command: "./a.out".to_string(), 117 | }, 118 | 119 | Language::CoffeeScript => RunInstructions { 120 | build_commands: vec![], 121 | run_command: format!("coffee {}", main_file_str), 122 | }, 123 | 124 | Language::Cpp => RunInstructions { 125 | build_commands: vec![format!( 126 | "clang++ -std=c++11 -o a.out {} {}", 127 | main_file_str, 128 | source_files(other_files, "c") 129 | )], 130 | run_command: "./a.out".to_string(), 131 | }, 132 | 133 | Language::Crystal => RunInstructions { 134 | build_commands: vec![], 135 | run_command: format!("crystal run {}", main_file_str), 136 | }, 137 | 138 | Language::Csharp => RunInstructions { 139 | build_commands: vec![format!( 140 | "mcs -out:a.exe {} {}", 141 | main_file_str, 142 | source_files(other_files, "cs") 143 | )], 144 | run_command: "mono a.exe".to_string(), 145 | }, 146 | 147 | Language::D => RunInstructions { 148 | build_commands: vec![format!( 149 | "dmd -ofa.out {} {}", 150 | main_file_str, 151 | source_files(other_files, "d") 152 | )], 153 | run_command: "./a.out".to_string(), 154 | }, 155 | 156 | Language::Dart => RunInstructions { 157 | build_commands: vec![], 158 | run_command: format!("dart {}", main_file_str), 159 | }, 160 | 161 | Language::Elixir => RunInstructions { 162 | build_commands: vec![], 163 | run_command: format!( 164 | "elixirc {} {}", 165 | main_file_str, 166 | source_files(other_files, "ex") 167 | ), 168 | }, 169 | 170 | Language::Elm => RunInstructions { 171 | build_commands: vec![format!("elm make --output a.js {}", main_file_str)], 172 | run_command: "elm-runner a.js".to_string(), 173 | }, 174 | 175 | Language::Erlang => RunInstructions { 176 | build_commands: filter_by_extension(other_files, "erl") 177 | .iter() 178 | .map(|file| format!("erlc {}", file.to_string_lossy())) 179 | .collect(), 180 | run_command: format!("escript {}", main_file_str), 181 | }, 182 | 183 | Language::Fsharp => { 184 | let mut source_files = filter_by_extension(other_files, "fs"); 185 | source_files.reverse(); 186 | 187 | RunInstructions { 188 | build_commands: vec![format!( 189 | "fsharpc --out:a.exe {} {}", 190 | space_separated_files(source_files), 191 | main_file_str 192 | )], 193 | run_command: "mono a.exe".to_string(), 194 | } 195 | } 196 | 197 | Language::Go => RunInstructions { 198 | build_commands: vec![format!("go build -o a.out {}", main_file_str)], 199 | run_command: "./a.out".to_string(), 200 | }, 201 | 202 | Language::Groovy => RunInstructions { 203 | build_commands: vec![], 204 | run_command: format!("groovy {}", main_file_str), 205 | }, 206 | 207 | Language::Guile => RunInstructions { 208 | build_commands: vec![], 209 | run_command: format!( 210 | "guile --no-debug --fresh-auto-compile --no-auto-compile -s {}", 211 | main_file_str 212 | ), 213 | }, 214 | 215 | Language::Hare => RunInstructions { 216 | build_commands: vec![format!("hare build -o a.out {}", main_file_str)], 217 | run_command: "./a.out".to_string(), 218 | }, 219 | 220 | Language::Haskell => RunInstructions { 221 | build_commands: vec![], 222 | run_command: format!("runghc {}", main_file_str), 223 | }, 224 | 225 | Language::Idris => RunInstructions { 226 | build_commands: vec![format!("idris2 -o a.out --output-dir . {}", main_file_str)], 227 | run_command: "./a.out".to_string(), 228 | }, 229 | 230 | Language::Java => { 231 | let file_stem = main_file 232 | .file_stem() 233 | .and_then(|s| s.to_str()) 234 | .unwrap_or("Main"); 235 | 236 | RunInstructions { 237 | build_commands: vec![format!("javac {}", main_file_str)], 238 | run_command: format!("java {}", titlecase_ascii(file_stem)), 239 | } 240 | } 241 | 242 | Language::JavaScript => RunInstructions { 243 | build_commands: vec![], 244 | run_command: format!("node {}", main_file_str), 245 | }, 246 | 247 | Language::Julia => RunInstructions { 248 | build_commands: vec![], 249 | run_command: format!("julia {}", main_file_str), 250 | }, 251 | 252 | Language::Kotlin => { 253 | let file_stem = main_file 254 | .file_stem() 255 | .and_then(|s| s.to_str()) 256 | .unwrap_or("Main"); 257 | 258 | RunInstructions { 259 | build_commands: vec![format!("kotlinc {}", main_file_str)], 260 | run_command: format!("kotlin {}Kt", titlecase_ascii(file_stem)), 261 | } 262 | } 263 | 264 | Language::Lua => RunInstructions { 265 | build_commands: vec![], 266 | run_command: format!("lua {}", main_file_str), 267 | }, 268 | 269 | Language::Mercury => RunInstructions { 270 | build_commands: vec![format!( 271 | "mmc -o a.out {} {}", 272 | main_file_str, 273 | source_files(other_files, "m") 274 | )], 275 | run_command: "./a.out".to_string(), 276 | }, 277 | 278 | Language::Nim => RunInstructions { 279 | build_commands: vec![], 280 | run_command: format!( 281 | "nim --hints:off --verbosity:0 compile --run {}", 282 | main_file_str 283 | ), 284 | }, 285 | 286 | Language::Nix => RunInstructions { 287 | build_commands: vec![], 288 | run_command: format!("nix-instantiate --eval {}", main_file_str), 289 | }, 290 | 291 | Language::Ocaml => { 292 | let mut source_files = filter_by_extension(other_files, "ml"); 293 | source_files.reverse(); 294 | 295 | RunInstructions { 296 | build_commands: vec![format!( 297 | "ocamlc -o a.out {} {}", 298 | space_separated_files(source_files), 299 | main_file_str 300 | )], 301 | run_command: "./a.out".to_string(), 302 | } 303 | } 304 | 305 | Language::Pascal => RunInstructions { 306 | build_commands: vec![format!("fpc -oa.out {}", main_file_str)], 307 | run_command: "./a.out".to_string(), 308 | }, 309 | 310 | Language::Perl => RunInstructions { 311 | build_commands: vec![], 312 | run_command: format!("perl {}", main_file_str), 313 | }, 314 | 315 | Language::Php => RunInstructions { 316 | build_commands: vec![], 317 | run_command: format!("php {}", main_file_str), 318 | }, 319 | 320 | Language::Python => RunInstructions { 321 | build_commands: vec![], 322 | run_command: format!("python {}", main_file_str), 323 | }, 324 | 325 | Language::Raku => RunInstructions { 326 | build_commands: vec![], 327 | run_command: format!("raku {}", main_file_str), 328 | }, 329 | 330 | Language::Ruby => RunInstructions { 331 | build_commands: vec![], 332 | run_command: format!("ruby {}", main_file_str), 333 | }, 334 | 335 | Language::Rust => RunInstructions { 336 | build_commands: vec![format!("rustc -o a.out {}", main_file_str)], 337 | run_command: "./a.out".to_string(), 338 | }, 339 | 340 | Language::SaC => RunInstructions { 341 | build_commands: vec![format!( 342 | "sac2c -t seq -o a.out {} {}", 343 | main_file_str, 344 | source_files(other_files, "c") 345 | )], 346 | run_command: "./a.out".to_string(), 347 | }, 348 | 349 | Language::Scala => RunInstructions { 350 | build_commands: vec![format!( 351 | "scalac {} {}", 352 | main_file_str, 353 | source_files(other_files, "scala") 354 | )], 355 | run_command: "scala Main".to_string(), 356 | }, 357 | 358 | Language::Swift => RunInstructions { 359 | build_commands: vec![format!( 360 | "swiftc -o a.out {} {}", 361 | main_file_str, 362 | source_files(other_files, "swift") 363 | )], 364 | run_command: "./a.out".to_string(), 365 | }, 366 | 367 | Language::TypeScript => RunInstructions { 368 | build_commands: vec![format!( 369 | "tsc -outFile a.js {} {}", 370 | main_file_str, 371 | source_files(other_files, "ts") 372 | )], 373 | run_command: "node a.js".to_string(), 374 | }, 375 | 376 | Language::Zig => RunInstructions { 377 | build_commands: vec![], 378 | run_command: format!("zig run {}", main_file_str), 379 | }, 380 | } 381 | } 382 | 383 | fn source_files(files: Vec, extension: &str) -> String { 384 | space_separated_files(filter_by_extension(files, extension)) 385 | } 386 | 387 | fn filter_by_extension(files: Vec, extension: &str) -> Vec { 388 | files 389 | .into_iter() 390 | .filter(|file| file.extension().and_then(|s| s.to_str()) == Some(extension)) 391 | .collect() 392 | } 393 | 394 | fn space_separated_files(files: Vec) -> String { 395 | files 396 | .iter() 397 | .map(|file| file.to_string_lossy().to_string()) 398 | .collect::>() 399 | .join(" ") 400 | } 401 | 402 | fn titlecase_ascii(s: &str) -> String { 403 | if !s.is_ascii() || s.len() < 2 { 404 | s.to_string() 405 | } else { 406 | let (head, tail) = s.split_at(1); 407 | format!("{}{}", head.to_ascii_uppercase(), tail) 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cmd; 2 | mod language; 3 | mod non_empty_vec; 4 | 5 | use language::RunInstructions; 6 | use std::env; 7 | use std::fmt; 8 | use std::fs; 9 | use std::io; 10 | use std::path; 11 | use std::path::Path; 12 | use std::process; 13 | use std::time; 14 | 15 | fn main() { 16 | let _ = start().map_err(handle_error); 17 | } 18 | 19 | fn handle_error(error: Error) { 20 | match error { 21 | // Print RunResult if it's a compile error 22 | Error::Compile(err) => { 23 | let run_result = to_error_result(err); 24 | let _ = serde_json::to_writer(io::stdout(), &run_result) 25 | .map_err(Error::SerializeRunResult) 26 | .map_err(handle_error); 27 | } 28 | 29 | _ => { 30 | eprintln!("{}", error); 31 | process::exit(1); 32 | } 33 | } 34 | } 35 | 36 | fn start() -> Result<(), Error> { 37 | let stdin = io::stdin(); 38 | let stdout = io::stdout(); 39 | let args = env::args().collect(); 40 | 41 | let run_request = parse_request(stdin)?; 42 | 43 | let work_path = match work_path_from_args(args) { 44 | Some(path) => path, 45 | 46 | None => default_work_path()?, 47 | }; 48 | 49 | // Some languages has a bootstrap file 50 | let bootstrap_file = Path::new("/bootstrap.tar.gz"); 51 | 52 | if bootstrap_file.exists() { 53 | unpack_bootstrap_file(&work_path, bootstrap_file)?; 54 | } 55 | 56 | let run_result = match run_request { 57 | RunRequest::V1(run_request) => run_v1(&work_path, run_request), 58 | RunRequest::V2(run_request) => run_v2(&work_path, run_request), 59 | }?; 60 | 61 | serde_json::to_writer(stdout, &run_result).map_err(Error::SerializeRunResult) 62 | } 63 | 64 | fn run_v1(work_path: &Path, run_request: RunRequestV1) -> Result { 65 | let files = run_request 66 | .files 67 | .into_iter() 68 | .map(|file| file_from_request_file(work_path, file)) 69 | .collect::, _>>()?; 70 | 71 | for file in &files { 72 | write_file(file)?; 73 | } 74 | 75 | match run_request.command { 76 | Some(command) if !command.is_empty() => { 77 | let run_result = run_command(work_path, &command, run_request.stdin); 78 | Ok(run_result) 79 | } 80 | 81 | Some(_) | None => { 82 | let file_paths = get_relative_file_paths(work_path, files)?; 83 | let run_instructions = language::run_instructions(&run_request.language, file_paths); 84 | run_by_instructions(work_path, &run_instructions, run_request.stdin) 85 | } 86 | } 87 | } 88 | 89 | fn run_v2(work_path: &Path, run_request: RunRequestV2) -> Result { 90 | let files = run_request 91 | .files 92 | .into_iter() 93 | .map(|file| file_from_request_file(work_path, file)) 94 | .collect::, _>>()?; 95 | 96 | for file in &files { 97 | write_file(file)?; 98 | } 99 | 100 | run_by_instructions(work_path, &run_request.run_instructions, run_request.stdin) 101 | } 102 | 103 | #[derive(serde::Serialize, Debug)] 104 | #[serde(rename_all = "camelCase")] 105 | struct RunResult { 106 | stdout: String, 107 | stderr: String, 108 | error: String, 109 | duration: u64, 110 | } 111 | 112 | fn to_success_result(output: cmd::SuccessOutput) -> RunResult { 113 | RunResult { 114 | stdout: output.stdout, 115 | stderr: output.stderr, 116 | error: "".to_string(), 117 | duration: output.duration.as_nanos() as u64, 118 | } 119 | } 120 | 121 | fn to_error_result(error: cmd::Error) -> RunResult { 122 | match error { 123 | cmd::Error::Output(cmd::OutputError::ExitFailure(output), duration) => RunResult { 124 | stdout: output.stdout, 125 | stderr: output.stderr, 126 | error: match output.exit_code { 127 | Some(exit_code) => { 128 | format!("Exit code: {}", exit_code) 129 | } 130 | 131 | None => "".to_string(), 132 | }, 133 | duration: duration.as_nanos() as u64, 134 | }, 135 | 136 | _ => RunResult { 137 | stdout: "".to_string(), 138 | stderr: "".to_string(), 139 | error: format!("{}", error), 140 | duration: error.duration().as_nanos() as u64, 141 | }, 142 | } 143 | } 144 | 145 | #[derive(serde::Deserialize, Debug)] 146 | #[serde(untagged)] 147 | enum RunRequest { 148 | V1(RunRequestV1), 149 | V2(RunRequestV2), 150 | } 151 | 152 | #[derive(serde::Deserialize, Debug)] 153 | #[serde(rename_all = "camelCase")] 154 | struct RunRequestV1 { 155 | language: language::Language, 156 | files: Vec, 157 | stdin: Option, 158 | command: Option, 159 | } 160 | 161 | #[derive(serde::Deserialize, Debug)] 162 | #[serde(rename_all = "camelCase")] 163 | struct RunRequestV2 { 164 | run_instructions: RunInstructions, 165 | files: Vec, 166 | stdin: Option, 167 | } 168 | 169 | #[derive(serde::Deserialize, Debug)] 170 | #[serde(rename_all = "camelCase")] 171 | struct RequestFile { 172 | name: String, 173 | content: String, 174 | } 175 | 176 | #[derive(Debug)] 177 | struct File { 178 | path: path::PathBuf, 179 | content: String, 180 | } 181 | 182 | fn file_from_request_file(base_path: &path::Path, file: RequestFile) -> Result { 183 | err_if_false(!file.name.is_empty(), Error::EmptyFileName())?; 184 | err_if_false(!file.content.is_empty(), Error::EmptyFileContent())?; 185 | 186 | Ok(File { 187 | path: base_path.join(file.name), 188 | content: file.content, 189 | }) 190 | } 191 | 192 | fn parse_request(reader: R) -> Result { 193 | serde_json::from_reader(reader).map_err(Error::ParseRequest) 194 | } 195 | 196 | fn work_path_from_args(arguments: Vec) -> Option { 197 | let args = arguments.iter().map(|s| s.as_ref()).collect::>(); 198 | 199 | match &args[1..] { 200 | ["--path", path] => Some(path::PathBuf::from(path)), 201 | 202 | _ => None, 203 | } 204 | } 205 | 206 | fn default_work_path() -> Result { 207 | let duration = time::SystemTime::now() 208 | .duration_since(time::UNIX_EPOCH) 209 | .map_err(Error::GetTimestamp)?; 210 | 211 | let name = format!("glot-{}", duration.as_secs()); 212 | 213 | Ok(env::temp_dir().join(name)) 214 | } 215 | 216 | fn unpack_bootstrap_file(work_path: &path::Path, bootstrap_file: &path::Path) -> Result<(), Error> { 217 | cmd::run(cmd::Options { 218 | work_path: work_path.to_path_buf(), 219 | command: format!("tar -zxf {}", bootstrap_file.to_string_lossy()), 220 | stdin: None, 221 | }) 222 | .map_err(Error::Bootstrap)?; 223 | 224 | Ok(()) 225 | } 226 | 227 | fn write_file(file: &File) -> Result<(), Error> { 228 | let parent_dir = file 229 | .path 230 | .parent() 231 | .ok_or_else(|| Error::GetParentDir(file.path.to_path_buf()))?; 232 | 233 | // Create parent directories 234 | fs::create_dir_all(parent_dir) 235 | .map_err(|err| Error::CreateParentDir(parent_dir.to_path_buf(), err))?; 236 | 237 | fs::write(&file.path, &file.content) 238 | .map_err(|err| Error::WriteFile(file.path.to_path_buf(), err)) 239 | } 240 | 241 | fn compile(work_path: &path::Path, command: &str) -> Result { 242 | cmd::run(cmd::Options { 243 | work_path: work_path.to_path_buf(), 244 | command: command.to_string(), 245 | stdin: None, 246 | }) 247 | .map_err(Error::Compile) 248 | } 249 | 250 | fn run_by_instructions( 251 | work_path: &Path, 252 | run_instructions: &RunInstructions, 253 | stdin: Option, 254 | ) -> Result { 255 | for command in &run_instructions.build_commands { 256 | compile(work_path, command)?; 257 | } 258 | 259 | let run_result = run_command(work_path, &run_instructions.run_command, stdin); 260 | Ok(run_result) 261 | } 262 | 263 | fn run_command(work_path: &path::Path, command: &str, stdin: Option) -> RunResult { 264 | let result = cmd::run(cmd::Options { 265 | work_path: work_path.to_path_buf(), 266 | command: command.to_string(), 267 | stdin, 268 | }); 269 | 270 | match result { 271 | Ok(output) => to_success_result(output), 272 | 273 | Err(err) => to_error_result(err), 274 | } 275 | } 276 | 277 | fn get_relative_file_paths( 278 | work_path: &path::Path, 279 | files: Vec, 280 | ) -> Result, Error> { 281 | let names = files 282 | .into_iter() 283 | .map(|file| { 284 | let path = file 285 | .path 286 | .strip_prefix(work_path) 287 | .map_err(Error::StripWorkPath)?; 288 | 289 | Ok(path.to_path_buf()) 290 | }) 291 | .collect::, Error>>()?; 292 | 293 | non_empty_vec::from_vec(names).ok_or(Error::NoFiles()) 294 | } 295 | 296 | enum Error { 297 | ParseRequest(serde_json::Error), 298 | NoFiles(), 299 | StripWorkPath(path::StripPrefixError), 300 | EmptyFileName(), 301 | EmptyFileContent(), 302 | GetTimestamp(time::SystemTimeError), 303 | GetParentDir(path::PathBuf), 304 | CreateParentDir(path::PathBuf, io::Error), 305 | WriteFile(path::PathBuf, io::Error), 306 | Bootstrap(cmd::Error), 307 | Compile(cmd::Error), 308 | SerializeRunResult(serde_json::Error), 309 | } 310 | 311 | impl fmt::Display for Error { 312 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 313 | match self { 314 | Error::ParseRequest(err) => { 315 | write!(f, "Failed to parse request json, {}", err) 316 | } 317 | 318 | Error::NoFiles() => { 319 | write!(f, "Error, no files were given") 320 | } 321 | 322 | Error::StripWorkPath(err) => { 323 | write!(f, "Failed to strip work path of file. {}", err) 324 | } 325 | 326 | Error::EmptyFileName() => { 327 | write!(f, "Error, file with empty name") 328 | } 329 | 330 | Error::EmptyFileContent() => { 331 | write!(f, "Error, file with empty content") 332 | } 333 | 334 | Error::GetTimestamp(err) => { 335 | write!(f, "Failed to get timestamp for work directory, {}", err) 336 | } 337 | 338 | Error::GetParentDir(file_path) => { 339 | write!( 340 | f, 341 | "Failed to get parent dir for file: '{}'", 342 | file_path.to_string_lossy() 343 | ) 344 | } 345 | 346 | Error::CreateParentDir(file_path, err) => { 347 | write!( 348 | f, 349 | "Failed to create parent dir for file '{}'. {}", 350 | file_path.to_string_lossy(), 351 | err 352 | ) 353 | } 354 | 355 | Error::WriteFile(file_path, err) => { 356 | write!( 357 | f, 358 | "Failed to write file: '{}'. {}", 359 | file_path.to_string_lossy(), 360 | err 361 | ) 362 | } 363 | 364 | Error::Bootstrap(err) => { 365 | write!(f, "Failed to unpack bootstrap file: {}", err) 366 | } 367 | 368 | Error::Compile(err) => { 369 | write!(f, "Failed to compile: {}", err) 370 | } 371 | 372 | Error::SerializeRunResult(err) => { 373 | write!(f, "Failed to serialize run result: {}", err) 374 | } 375 | } 376 | } 377 | } 378 | 379 | fn err_if_false(value: bool, err: E) -> Result<(), E> { 380 | if value { 381 | Ok(()) 382 | } else { 383 | Err(err) 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/non_empty_vec.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct NonEmptyVec { 3 | head: T, 4 | tail: Vec, 5 | } 6 | 7 | impl NonEmptyVec { 8 | pub fn parts(self) -> (T, Vec) { 9 | (self.head, self.tail) 10 | } 11 | } 12 | 13 | pub fn from_vec(mut vec: Vec) -> Option> { 14 | if vec.is_empty() { 15 | None 16 | } else { 17 | let head = vec.remove(0); 18 | 19 | Some(NonEmptyVec { head, tail: vec }) 20 | } 21 | } 22 | --------------------------------------------------------------------------------