NomicFoundation / hardhat

Hardhat is a development environment to compile, deploy, test, and debug your Ethereum software.
https://hardhat.org
Other
7.33k stars 1.42k forks source link

hardhat.config.js paths object should allow for array fields #776

Open wighawag opened 4 years ago

wighawag commented 4 years ago

For my plugin, I would like path config to be arrays.

Unfortunately, while buidler allow the types to be arrays, it fails at runtime because builder attempt to read them as string

wighawag commented 4 years ago

Actually, ideally, if they could be any type, it would be even better.

alcuadrado commented 4 years ago

Can you tell us more about what you are trying to do?

wighawag commented 4 years ago

I basically want user to set multiple path for artifact and deployments, see : https://github.com/wighawag/buidler-deploy/issues/34

So instead of having the following in buidler.config.js

paths: {
    imports: ["node_modules/@cartesi/arbitration/build/contracts"],
    externalDeployments: {
      rinkeby: ["node_modules/@cartesi/arbitration/build/contracts"],
    }
}

I need to add the paths to a different field:

external: {
    artifacts: ["node_modules/@cartesi/arbitration/build/contracts"],
    deployments: {
      rinkeby: ["node_modules/@cartesi/arbitration/build/contracts"],
    },
  },

Actually thinking about it the deployments object could be set on the network config, like that

networks: {
    extraDeployments: ["node_modules/@cartesi/arbitration/build/contracts"]
},

But this is just an example. Basically there are use case where paths need to be arrays and potentially objects. buidler type system allows for it, but this fails at runtime.

fvictorio commented 4 years ago

Can you share the stack trace where it fails at runtime? If I'm understanding correctly, this shouldn't happen (I mean, fields other than the "predefined" ones shouldn't be used in any way.)

wighawag commented 4 years ago

i forgot what was the exact error, I ll see if I can get it again, but looking at the code here : https://github.com/nomiclabs/buidler/blob/0183504555c2aed2e3ea7e63b6ddeb656ce7d4f7/packages/buidler-core/src/internal/core/config/config-resolution.ts#L95 it expected the paths to be string

fvictorio commented 4 years ago

Oh, I see. I'm not sure what the right solution is here. I guess we could stop resolving the other entries from paths and just pass them down as they are in the buidler config, but this is a breaking change.

mdcoon commented 4 years ago

Another scenario for this is for monorepos where I have contracts spread out across individual packages. I want to leverage waffle test config setting "compilerAllowedPaths" but it doesn't look like it's supported. I basically want something like this:

paths: { compilerAllowedPaths: ['../common', '../token'] }

or paths: { sourcesPath: ['../common/contracts', '../token/contracts'], artifacts: ['../common/artifacts', '../token/artifacts'] }

krasi-georgiev commented 3 years ago

Just to add my use case as well. I want to keep all test dependency contracts in a separate folder so this is required to be able to use deploy plugin with a custom folder only for given contracts.

bricedenice59 commented 2 years ago

More than one year later, is there any news about this? I have multiple contracts scattered into multiple solution folders, each contract is a proper hardhat project. In my "main project", I have multiple tests and some depends on other contracts that are not in my main solution contract folder. I want to deploy the other contracts by using the ethers.getContractFactory(), unfortunately it crashes. How to do that if we cannot specify multiple path sources? Error: Invalid value ["./contracts","../../anotherContractPath/contracts"] for HardhatConfig.paths.sources - Expected a value of type string | undefined. Thanks.

seaona commented 1 year ago

I'm in the same situation as @bricedenice59. Is there a workaround for this issue while it's not resolved? I would really appreciate some advice here. Thank you!!

stephancill commented 1 year ago

Would also really appreciate this feature!

fergarrui commented 1 year ago

Any news on this issue?

ghost commented 1 year ago

Any news on this? I would really need this feature, as I have an hardhat repo with some contracts, but I also have another hardhat repo with other contracts that is a submodule of the 1st one. I would like to deploy the contracts all together but I cannot because I can only set 1 single path for each kind (1 artifacts, 1 sources etc)

lekevicius commented 1 year ago

Joining in. We're approaching 3 year anniversary for this issue (:

Also, I don't see how this is a breaking change: old behavior can easily be supported. Allow strings or arrays.

fvictorio commented 1 year ago

@lekevicius I know it's hard to see how this is a breaking change, but assume you are a plugin author that uses the paths.sources somehow. The type of this is string, so your code does something assuming it is a string.

Then Hardhat updates to a new patch/minor version and suddenly sources is string | string[]. Your code very likely won't work when you receive an array. So it's not a breaking change from a user perspective, but it can be from a plugin author perspective.

That being said:

Hopefully that makes sense, happy to answer any questions about this.

tariqkb commented 1 year ago

This is easy to do with a plugin or overriding a subtask. I know that that approach has way more friction than having this as a built-in feature, but the fact that there is a workaround also makes me err on the side of not introducing a breaking change.

Hello @fvictorio, do you have an example I can look at to do this? Is it TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS that needs overriding?

fvictorio commented 1 year ago

Something like this should work:

const glob = require("glob");
const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require("hardhat/builtin-tasks/task-names");
const path = require("path");

subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, hre, runSuper) => {
  const paths = await runSuper();

  const otherDirectoryGlob = path.join(hre.config.paths.root, "more-contracts", "**", "*.sol");
  const otherPaths = glob.sync(otherDirectoryGlob);

  return [...paths, ...otherPaths];
});
cekingx commented 1 year ago

Something like this should work:

const glob = require("glob");
const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require("hardhat/builtin-tasks/task-names");
const path = require("path");

subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, hre, runSuper) => {
  const paths = await runSuper();

  const otherDirectoryGlob = path.join(hre.config.paths.root, "more-contracts", "**", "*.sol");
  const otherPaths = glob.sync(otherDirectoryGlob);

  return [...paths, ...otherPaths];
});

thank you

timbrinded commented 3 months ago

Sadly this 4 year old bug means a lot of friction when trying to use hardhat to compile projects which don't conform to this single contract dir structure.

A not uncommon example is to use hardhat to compile a git submodule which uses foundry, which as you know uses solidity files for scripts and tests. Being able to specify individual paths would go a long way in terms of basic ergonomics.

s-di-cola commented 1 month ago

If anyone is stuck on this, here is a possible working solution:

import { HardhatUserConfig, task } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox-viem";
import "@nomiclabs/hardhat-solhint";
import { glob } from "glob";
import path from "path";

// Define the base configuration
const config: HardhatUserConfig = {
  solidity: "0.8.27",
  paths: {
    sources: "./contracts", // This will be overridden in the compile task
    tests: "./test", // This will be overridden in the test task
  },
};

// Helper function to find contract directories
const findContractDirs = (rootDir: string) => {
  const contractPaths = glob.sync("module-*/deliverables/**/*.sol", {
    cwd: rootDir,
    ignore: ["**/node_modules/**"],
  });
  return [...new Set(contractPaths.map(path.dirname))];
};

// Override the default compile task
task(
  "compile",
  "Compiles the entire project, running all compilations",
).setAction(async (taskArgs, hre, runSuper) => {
  const rootDir = hre.config.paths.root;
  const contractDirs = findContractDirs(rootDir);

  for (const contractDir of contractDirs) {
    console.log(`Compiling contracts in ${contractDir}...`);
    hre.config.paths.sources = path.join(rootDir, contractDir);
    await runSuper(taskArgs);
  }
});

// Custom test task
task("test", "Runs mocha tests in all module-x/test folders").setAction(
  async (taskArgs, hre, runSuper) => {
    const rootDir = hre.config.paths.root;
    const testFolders = glob.sync("module-*/test", { cwd: rootDir });

    // First, run the compile task
    await hre.run("compile");

    for (const testFolder of testFolders) {
      console.log(`Running tests in ${testFolder}...`);
      hre.config.paths.tests = path.join(rootDir, testFolder);
      await runSuper(taskArgs);
    }
  },
);

export default config;

Just change the glob pattern for both source and test. This configuration simply loop through your contracts and tests.