Skip to content

Commit 4746896

Browse files
committed
Fix the issue with nightly rust breaking change
Introduced the nightly toolchain access for anchor and bumped its version
1 parent 0d4f5f3 commit 4746896

File tree

7 files changed

+298
-12
lines changed

7 files changed

+298
-12
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
result*
2-
example/*
2+
example/*
3+
.direnv

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Solana Nix
22

3-
Solana nix setup for compiling programs.
3+
Solana and Anchor nix setup for compiling programs.
44

5-
This is not the latest solana or anchor, ensure your project matches the packaged solana version:
5+
## Dev shell
66

7-
for example to move from 1.18.12 in your initialised anchor project
7+
`anchor` and `solana` binaries are available in the dev shell
8+
9+
## Rust nightly
10+
11+
Unfortunately anchor depends on nightly rust complier (a very unstable and common practice in rust projects)
12+
13+
## Changing solana version
814

915
```sh
1016
cargo update -p [email protected] --precise 1.17.28

anchor-cli.nix

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@
1010
rust-bin,
1111
udev,
1212
crane,
13-
version ? "0.31.0",
13+
runCommand,
14+
version ? "0.31.1",
1415
}:
1516
let
1617
pname = "anchor-cli";
1718

1819
versionsDeps = {
20+
"0.31.1" = {
21+
hash = "sha256-c+UybdZCFL40TNvxn0PHR1ch7VPhhJFDSIScetRpS3o=";
22+
# Unfortunately dependency on nightly compiler seems to be common
23+
# in rust projects
24+
rust-nightly = rust-bin.nightly."2025-04-21".minimal;
25+
rust = rust-bin.stable."1.85.0".default;
26+
platform-tools = solana-platform-tools.override { version = "1.45"; };
27+
patches = [ ./patches/anchor-cli/0.31.1.patch ];
28+
};
1929
"0.31.0" = {
2030
hash = "sha256-CaBVdp7RPVmzzEiVazjpDLJxEkIgy1BHCwdH2mYLbGM=";
2131
rust = rust-bin.stable."1.85.0".default;
@@ -86,10 +96,29 @@ craneLib.buildPackage (
8696
inherit cargoArtifacts;
8797

8898
# Ensure anchor has access to Solana's cargo and rust binaries
89-
postInstall = ''
99+
postInstall = let
100+
# Due to th way anchor is calling cargo if its not wrapped
101+
# with its own toolchain, it'll access solana rust compiler instead
102+
# hence the nightly entry points must be wrapped with the nightly bins
103+
# to guarantee correct usage
104+
# In this case we've limited the nightly bin access to `cargo`
105+
cargo-nightly = runCommand "cargo-nightly" {
106+
nativeBuildInputs = [ makeWrapper ];
107+
} ''
108+
mkdir -p $out/bin
109+
110+
ln -s ${versionDeps.rust-nightly}/bin/cargo $out/bin/cargo
111+
112+
# Wrap cargo nightly so it uses the nightly toolchain only
113+
wrapProgram $out/bin/cargo \
114+
--prefix PATH : "${versionDeps.rust-nightly}/bin"
115+
''
116+
;
117+
in
118+
''
90119
rust=${versionDeps.platform-tools}/bin/platform-tools-sdk/sbf/dependencies/platform-tools/rust/bin
91120
wrapProgram $out/bin/anchor \
92-
--prefix PATH : "$rust"
121+
--prefix PATH : "$rust" ${if versionDeps ? rust-nightly then "--set RUST_NIGHTLY_BIN \"${cargo-nightly}/bin\"" else ""}
93122
'';
94123

95124
cargoExtraArgs = "-p ${pname}";

anchor-test.nix

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
stdenv,
3+
anchor-cli,
4+
runCommand,
5+
}:
6+
let
7+
src = runCommand "anchor-test-src"
8+
{
9+
nativeBuildInputs = [ anchor-cli ];
10+
}
11+
''
12+
mkdir -p $out
13+
cd $out
14+
anchor init example -j --no-install --no-git --test-template rust
15+
mv example/.* example/* .
16+
rm -rf example
17+
'';
18+
in
19+
stdenv.mkDerivation {
20+
pname = "anchor-test";
21+
inherit src;
22+
version = "1.0.0";
23+
24+
doCheck = false;
25+
26+
nativeBuildInputs = [ anchor-cli ];
27+
28+
buildPhase = ''
29+
anchor build
30+
anchor test
31+
'';
32+
installPhase = ''
33+
echo ok > $out
34+
'';
35+
36+
meta = {
37+
description = "Anchor build testing";
38+
};
39+
}

flake.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
flake-parts.url = "github:hercules-ci/flake-parts/f4330d22f1c5d2ba72d3d22df5597d123fdb60a9";
66
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
77
rust-overlay = {
8-
url = "github:oxalica/rust-overlay/954582a766a50ebef5695a9616c93b5386418c08";
8+
url = "github:oxalica/rust-overlay/bd32e88bef6da0e021a42fb4120a8df2150e9b8c";
99
inputs.nixpkgs.follows = "nixpkgs";
1010
};
1111
crane.url = "github:ipetkov/crane";
@@ -53,6 +53,9 @@
5353
inherit solana-platform-tools;
5454
crane = crane.mkLib pkgs;
5555
};
56+
anchor-test = pkgs.callPackage ./anchor-test.nix {
57+
inherit anchor-cli;
58+
};
5659
in
5760
{
5861
overlayAttrs = {
@@ -62,6 +65,7 @@
6265
solana-rust
6366
solana-cli
6467
anchor-cli
68+
anchor-test
6569
;
6670
};
6771
_module.args.pkgs = import inputs.nixpkgs {
@@ -89,6 +93,7 @@
8993
solana-cli
9094
solana-platform-tools
9195
solana-rust
96+
anchor-test
9297
;
9398
};
9499
};

patches/anchor-cli/0.31.1.patch

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
diff --git a/cli/src/lib.rs b/cli/src/lib.rs
2+
index 3404b032..34b08dac 100644
3+
--- a/cli/src/lib.rs
4+
+++ b/cli/src/lib.rs
5+
@@ -11,7 +11,6 @@ use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTyp
6+
use anyhow::{anyhow, Context, Result};
7+
use checks::{check_anchor_version, check_deps, check_idl_build_feature, check_overflow};
8+
use clap::{CommandFactory, Parser};
9+
-use dirs::home_dir;
10+
use flate2::read::GzDecoder;
11+
use flate2::read::ZlibDecoder;
12+
use flate2::write::{GzEncoder, ZlibEncoder};
13+
@@ -552,7 +551,7 @@ type RestoreToolchainCallbacks = Vec<Box<dyn FnOnce() -> Result<()>>>;
14+
///
15+
/// Returns the previous versions to restore back to.
16+
fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainCallbacks> {
17+
- let mut restore_cbs: RestoreToolchainCallbacks = vec![];
18+
+ let restore_cbs: RestoreToolchainCallbacks = vec![];
19+
20+
let cfg = Config::discover(cfg_override)?;
21+
if let Some(cfg) = cfg {
22+
@@ -584,88 +583,7 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
23+
if let Some(solana_version) = &cfg.toolchain.solana_version {
24+
let current_version = get_current_version("solana")?;
25+
if solana_version != &current_version {
26+
- // We are overriding with `solana-install` command instead of using the binaries
27+
- // from `~/.local/share/solana/install/releases` because we use multiple Solana
28+
- // binaries in various commands.
29+
- fn override_solana_version(version: String) -> Result<bool> {
30+
- // There is a deprecation warning message starting with `1.18.19` which causes
31+
- // parsing problems https://github.com/coral-xyz/anchor/issues/3147
32+
- let (cmd_name, domain) =
33+
- if Version::parse(&version)? < Version::parse("1.18.19")? {
34+
- ("solana-install", "solana.com")
35+
- } else {
36+
- ("agave-install", "anza.xyz")
37+
- };
38+
-
39+
- // Install the command if it's not installed
40+
- if get_current_version(cmd_name).is_err() {
41+
- // `solana-install` and `agave-install` are not usable at the same time i.e.
42+
- // using one of them makes the other unusable with the default installation,
43+
- // causing the installation process to run each time users switch between
44+
- // `agave` supported versions. For example, if the user's active Solana
45+
- // version is `1.18.17`, and he specifies `solana_version = "2.0.6"`, this
46+
- // code path will run each time an Anchor command gets executed.
47+
- eprintln!(
48+
- "Command not installed: `{cmd_name}`. \
49+
- See https://github.com/anza-xyz/agave/wiki/Agave-Transition, \
50+
- installing..."
51+
- );
52+
- let install_script = std::process::Command::new("curl")
53+
- .args([
54+
- "-sSfL",
55+
- &format!("https://release.{domain}/v{version}/install"),
56+
- ])
57+
- .output()?;
58+
- let is_successful = std::process::Command::new("sh")
59+
- .args(["-c", std::str::from_utf8(&install_script.stdout)?])
60+
- .spawn()?
61+
- .wait_with_output()?
62+
- .status
63+
- .success();
64+
- if !is_successful {
65+
- return Err(anyhow!("Failed to install `{cmd_name}`"));
66+
- }
67+
- }
68+
-
69+
- let output = std::process::Command::new(cmd_name).arg("list").output()?;
70+
- if !output.status.success() {
71+
- return Err(anyhow!("Failed to list installed `solana` versions"));
72+
- }
73+
-
74+
- // Hide the installation progress if the version is already installed
75+
- let is_installed = std::str::from_utf8(&output.stdout)?
76+
- .lines()
77+
- .filter_map(parse_version)
78+
- .any(|line_version| line_version == version);
79+
- let (stderr, stdout) = if is_installed {
80+
- (Stdio::null(), Stdio::null())
81+
- } else {
82+
- (Stdio::inherit(), Stdio::inherit())
83+
- };
84+
-
85+
- std::process::Command::new(cmd_name)
86+
- .arg("init")
87+
- .arg(&version)
88+
- .stderr(stderr)
89+
- .stdout(stdout)
90+
- .spawn()?
91+
- .wait()
92+
- .map(|status| status.success())
93+
- .map_err(|err| anyhow!("Failed to run `{cmd_name}` command: {err}"))
94+
- }
95+
-
96+
- match override_solana_version(solana_version.to_owned())? {
97+
- true => restore_cbs.push(Box::new(|| {
98+
- match override_solana_version(current_version)? {
99+
- true => Ok(()),
100+
- false => Err(anyhow!("Failed to restore `solana` version")),
101+
- }
102+
- })),
103+
- false => eprintln!(
104+
- "Failed to override `solana` version to {solana_version}, \
105+
- using {current_version} instead"
106+
- ),
107+
- }
108+
+ return Err(anyhow!("Current Solana version `{current_version}` does not match configured version `{solana_version}`"));
109+
}
110+
}
111+
112+
@@ -688,40 +606,7 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
113+
.unwrap_or(VERSION)
114+
.to_owned();
115+
if anchor_version != &current_version {
116+
- let binary_path = home_dir()
117+
- .unwrap()
118+
- .join(".avm")
119+
- .join("bin")
120+
- .join(format!("{ANCHOR_BINARY_PREFIX}{anchor_version}"));
121+
-
122+
- if !binary_path.exists() {
123+
- eprintln!(
124+
- "`anchor` {anchor_version} is not installed with `avm`. Installing...\n"
125+
- );
126+
-
127+
- let exit_status = std::process::Command::new("avm")
128+
- .arg("install")
129+
- .arg(anchor_version)
130+
- .spawn()?
131+
- .wait()?;
132+
- if !exit_status.success() {
133+
- eprintln!(
134+
- "Failed to install `anchor` {anchor_version}, \
135+
- using {current_version} instead"
136+
- );
137+
-
138+
- return Ok(restore_cbs);
139+
- }
140+
- }
141+
-
142+
- let exit_code = std::process::Command::new(binary_path)
143+
- .args(std::env::args_os().skip(1))
144+
- .spawn()?
145+
- .wait()?
146+
- .code()
147+
- .unwrap_or(1);
148+
- restore_toolchain(restore_cbs)?;
149+
- std::process::exit(exit_code);
150+
+ return Err(anyhow!("Current Anchor version `{current_version}` does not match configured version `{anchor_version}`"));
151+
}
152+
}
153+
}
154+
diff --git a/idl/src/build.rs b/idl/src/build.rs
155+
index ccd89745..3c958377 100644
156+
--- a/idl/src/build.rs
157+
+++ b/idl/src/build.rs
158+
@@ -139,14 +139,18 @@ fn build(
159+
cargo_args: &[String],
160+
) -> Result<Idl> {
161+
// `nightly` toolchain is currently required for building the IDL.
162+
- let toolchain = std::env::var("RUSTUP_TOOLCHAIN")
163+
- .map(|toolchain| format!("+{}", toolchain))
164+
- .unwrap_or_else(|_| "+nightly".to_string());
165+
+ let cargo_path = std::env::var("RUST_NIGHTLY_BIN")
166+
+ .map(|bin| format!("{}/cargo", bin))
167+
+ .unwrap_or_else(|_| "cargo".to_string());
168+
169+
- install_toolchain_if_needed(&toolchain)?;
170+
- let output = Command::new("cargo")
171+
+ eprintln!("Cargo at {}", cargo_path);
172+
+
173+
+ let mut command = Command::new(cargo_path);
174+
+ if let Ok(toolchain) = std::env::var("RUSTUP_TOOLCHAIN") {
175+
+ command.arg(&format!("+{}", toolchain));
176+
+ }
177+
+ let output = command
178+
.args([
179+
- &toolchain,
180+
"test",
181+
"__anchor_private_print_idl",
182+
"--features",
183+
@@ -283,23 +287,6 @@ fn build(
184+
idl.ok_or_else(|| anyhow!("IDL doesn't exist"))
185+
}
186+
187+
-/// Install the given toolchain if it's not already installed.
188+
-fn install_toolchain_if_needed(toolchain: &str) -> Result<()> {
189+
- let is_installed = Command::new("cargo")
190+
- .arg(toolchain)
191+
- .output()?
192+
- .status
193+
- .success();
194+
- if !is_installed {
195+
- Command::new("rustup")
196+
- .args(["toolchain", "install", toolchain.trim_start_matches('+')])
197+
- .spawn()?
198+
- .wait()?;
199+
- }
200+
-
201+
- Ok(())
202+
-}
203+
-
204+
/// Convert paths to name if there are no conflicts.
205+
fn convert_module_paths(idl: Idl) -> Idl {
206+
let idl = serde_json::to_string(&idl).unwrap();

0 commit comments

Comments
 (0)