Brendonovich / prisma-client-rust

Type-safe database access for Rust
https://prisma.brendonovich.dev
Apache License 2.0
1.75k stars 106 forks source link

Using Inner Attributes makes it impossible to include code generation from build script. #306

Closed MinisculeGirraffe closed 1 year ago

MinisculeGirraffe commented 1 year ago

Howdy.

I was working on testing running code-generation in a build script, which I feel is preferable for multiple reasons. You can have the code automatically re-run at build time every time a file in the /prisma folder changes. With the benefit of it not being included in your actual /src folder so there's no way you can git commit or modify the file that gets code-generated.

I've got two crates in my workspace:

In the build script, we're executing the prisma-cli command to codegen, and setting an env variable to change the output of the generated file to be in the /target directory.

build.rs

use std::{env, path::Path, process::Command};

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let codegen_dest_path = Path::new(&out_dir).join("generated.rs");
    env::set_var("PRISMA_OUT_FILE", codegen_dest_path);
    env::set_var("CARGO_TARGET_DIR", &out_dir);
    let output = Command::new("cargo")
        .args(&["run", "-p", "prisma-cli", "generate"])
        .output()
        .expect("Failed run to codegen command");

    println!("cargo:rerun-if-changed=/prisma");
}

schema.prisma

generator client {
    // Corresponds to the cargo alias created earlier
    provider      = "cargo prisma"
    // The location to generate the client. Is relative to the position of the schema
    output        =  env("PRISMA_OUT_FILE")
}

lib.rs

include!(concat!(env!("OUT_DIR"), "/generated.rs"));

This all works. The problem is that the generated code uses #![allow(warnings, unused)] which isn't allowed when using an include! to bring the generated code into our libraries context. Compiler error for reference:

error: an inner attribute is not permitted in this context
 --> /Users/dnorred/app/back-end/target/debug/build/prisma-codegen-dd61d46b0c3f1ab5/out/generated.rs:3:1
  |
3 |   #![allow(warnings, unused)]
  |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 | / pub static DATAMODEL_STR: &'static str =
5 | |     include_str!("/Users/dnorred/app/back-end/prisma-codegen/prisma/schema.prisma");
  | |____________________________________________________________________________________- the inner attribute doesn't annotate this static item
  |
  = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files
help: to annotate the static item, change the attribute from inner to outer style
  |
3 - #![allow(warnings, unused)]
3 + #[allow(warnings, unused)]
  |

Seems like this issue has happened with other projects that use code generation as well

https://github.com/google/flatbuffers/issues/6261

I think the fix would be to just replace it.

MinisculeGirraffe commented 1 year ago

This might sound dumb, but I just replaced that line in the build script with the recommendation from the compiler error message and that fixed it.

I'm able to use the "empty" prisma-codegen crate without having to write any files to /src, and the contents automatically get re-generated whenever I modify the schema file.

main.rs from the application workspace crate:

use prisma_codegen::{new_client_with_url, PrismaClient};
use color_eyre::Result;

/// Create a new prisma client, and apply any pending migrations
async fn prisma_client(url: &str) -> Result<PrismaClient> {
    let client = new_client_with_url(url).await?;

    #[cfg(debug_assertions)]
    client._db_push().await?;
    #[cfg(not(debug_assertions))]
    client._migrate_deploy().await?;
    Ok(client)
}

build.rs after modifications:

use std::io::{BufRead, BufReader};
use std::{env, path::Path, process::Command};
use std::{fs, fs::File};

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let codegen_dest_path = Path::new(&out_dir).join("generated.rs");
    env::set_var("PRISMA_OUT_FILE", &codegen_dest_path);
    env::set_var("CARGO_TARGET_DIR", &out_dir);
    let output = Command::new("cargo")
        .args(&["run", "-p", "prisma-cli", "generate"])
        .output()
        .expect("Failed run to codegen command");

    let mut lines = lines_from_file(&codegen_dest_path);

    for line in &mut lines {
        if line.contains("#!") {
            *line = line.replace("#!", "#");
        }
    }

    fs::write(&codegen_dest_path, lines.join("\n")).expect("Failed to update file");
    println!("cargo:rerun-if-changed=/prisma");
}

fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines()
        .map(|l| l.expect("Could not parse line"))
        .collect()
}
Brendonovich commented 1 year ago

Huh, never knew that include! worked like that. I also never knew that if you put #[allow(...)] on a mod ...; declaration it'll do the same as the inner attributes I'm currently using. I think I'm going to get rid of them all together and recommend that users add the attributes themselves, this way you can either add them on the mod ...; or inject them at the top of the file in your build script kind of like the text replacement you're already doing.

MinisculeGirraffe commented 1 year ago

FWIW I've had absolutely zero issues with the build script since then. I think it's the preferable way to go since every cargo-check will make sure you've got the latest codegen from the prisma schema. One less thing to worry about being fragile.

Appreciate the change. Hopefully I can remove that extra bit from the build script 😅.