Skip to content

Commit

Permalink
Merge branch 'coral-xyz:master' into fix/spl-token-upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
yanCode authored Dec 23, 2024
2 parents 7dbe334 + 6ff6655 commit 9abc66a
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 85 deletions.
88 changes: 88 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Release

on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"

pull_request:
branches:
- master
paths:
- VERSION

env:
DIST: dist-${{ github.ref_name }}

jobs:
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
target:
- aarch64-apple-darwin
- x86_64-unknown-linux-gnu
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
include:
- target: aarch64-apple-darwin
os: macos-latest

- target: x86_64-unknown-linux-gnu
os: ubuntu-latest

- target: x86_64-apple-darwin
os: macos-latest

- target: x86_64-pc-windows-msvc
os: windows-latest

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
target: ${{ matrix.target }}

- name: Build release binary
run: cargo build --package anchor-cli --release --locked --target ${{ matrix.target }}

- name: Prepare
if: startsWith(github.ref, 'refs/tags/')
id: prepare
shell: bash
run: |
version=$(echo $GITHUB_REF_NAME | cut -dv -f2)
ext=""
[[ "${{ matrix.os }}" == windows-latest ]] && ext=".exe"
mkdir $DIST
mv "target/${{ matrix.target }}/release/anchor$ext" $DIST/anchor-$version-${{ matrix.target }}$ext
echo "version=$version" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
if: startsWith(github.ref, 'refs/tags/')
with:
name: anchor-${{ steps.prepare.outputs.version }}-${{ matrix.target }}
path: ${{ env.DIST }}
overwrite: true
retention-days: 1

upload:
name: Upload binaries to release
if: startsWith(github.ref, 'refs/tags/')
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
path: ${{ env.DIST }}

- name: Upload
shell: bash
run: GH_TOKEN=${{ secrets.GITHUB_TOKEN }} gh release upload $GITHUB_REF_NAME $DIST/*/* --clobber
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- cli: Add test template for [Mollusk](https://github.com/buffalojoec/mollusk) ([#3352](https://github.com/coral-xyz/anchor/pull/3352)).
- idl: Disallow account discriminators that can conflict with the `zero` constraint ([#3365](https://github.com/coral-xyz/anchor/pull/3365)).
- cli: Include recommended solana args by default and add new `--max-retries` option to the `deploy` command ([#3354](https://github.com/coral-xyz/anchor/pull/3354)).
- avm: Make installation download binaries by default ([#3445](https://github.com/coral-xyz/anchor/pull/3445)).

### Fixes

Expand Down Expand Up @@ -103,6 +104,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- lang: Require `zero` accounts to be unique ([#3409](https://github.com/coral-xyz/anchor/pull/3409)).
- lang: Deduplicate `zero` accounts against `init` accounts ([#3422](https://github.com/coral-xyz/anchor/pull/3422)).
- cli: Fix custom `provider.cluster` ([#3428](https://github.com/coral-xyz/anchor/pull/3428)).
- cli: Ignore non semver solana/agave releases to avoid panic ([#3432](https://github.com/coral-xyz/anchor/pull/3432)).

### Breaking

Expand Down
175 changes: 105 additions & 70 deletions avm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub fn use_version(opt_version: Option<Version>) -> Result<()> {
.next()
.expect("Expected input")?;
match input.as_str() {
"y" | "yes" => return install_version(InstallTarget::Version(version), false),
"y" | "yes" => return install_version(InstallTarget::Version(version), false, false),
_ => return Err(anyhow!("Installation rejected.")),
};
}
Expand All @@ -107,7 +107,7 @@ pub enum InstallTarget {
/// Update to the latest version
pub fn update() -> Result<()> {
let latest_version = get_latest_version()?;
install_version(InstallTarget::Version(latest_version), false)
install_version(InstallTarget::Version(latest_version), false, false)
}

/// The commit sha provided can be shortened,
Expand Down Expand Up @@ -165,84 +165,119 @@ fn get_anchor_version_from_commit(commit: &str) -> Result<Version> {
}

/// Install a version of anchor-cli
pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()> {
let mut args: Vec<String> = vec![
"install".into(),
"--git".into(),
"https://github.com/coral-xyz/anchor".into(),
"anchor-cli".into(),
"--locked".into(),
"--root".into(),
AVM_HOME.to_str().unwrap().into(),
];
let version = match install_target {
InstallTarget::Version(version) => {
args.extend(["--tag".into(), format!("v{}", version), "anchor-cli".into()]);
version
}
InstallTarget::Commit(commit) => {
args.extend(["--rev".into(), commit.clone()]);
get_anchor_version_from_commit(&commit)?
}
pub fn install_version(
install_target: InstallTarget,
force: bool,
from_source: bool,
) -> Result<()> {
let version = match &install_target {
InstallTarget::Version(version) => version.to_owned(),
InstallTarget::Commit(commit) => get_anchor_version_from_commit(commit)?,
};

// If version is already installed we ignore the request.
let installed_versions = read_installed_versions()?;
if installed_versions.contains(&version) && !force {
println!("Version {version} is already installed");
// Return early if version is already installed
if !force && read_installed_versions()?.contains(&version) {
eprintln!("Version `{version}` is already installed");
return Ok(());
}

// If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem
// explained in https://github.com/coral-xyz/anchor/pull/3143
if version < Version::parse("0.31.0")? {
const REQUIRED_VERSION: &str = "1.79.0";
let is_installed = Command::new("rustup")
.args(["toolchain", "list"])
.output()
.map(|output| String::from_utf8(output.stdout))??
.lines()
.any(|line| line.starts_with(REQUIRED_VERSION));
if !is_installed {
let exit_status = Command::new("rustup")
.args(["toolchain", "install", REQUIRED_VERSION])
.spawn()?
.wait()?;
if !exit_status.success() {
return Err(anyhow!(
"Installation of `rustc {REQUIRED_VERSION}` failed. \
let is_older_than_v0_31_0 = version < Version::parse("0.31.0")?;
if from_source || is_older_than_v0_31_0 {
// Build from source using `cargo install --git`
let mut args: Vec<String> = vec![
"install".into(),
"anchor-cli".into(),
"--git".into(),
"https://github.com/coral-xyz/anchor".into(),
"--locked".into(),
"--root".into(),
AVM_HOME.to_str().unwrap().into(),
];
let conditional_args = match install_target {
InstallTarget::Version(version) => ["--tag".into(), format!("v{}", version)],
InstallTarget::Commit(commit) => ["--rev".into(), commit],
};
args.extend_from_slice(&conditional_args);

// If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem
// explained in https://github.com/coral-xyz/anchor/pull/3143
if is_older_than_v0_31_0 {
const REQUIRED_VERSION: &str = "1.79.0";
let is_installed = Command::new("rustup")
.args(["toolchain", "list"])
.output()
.map(|output| String::from_utf8(output.stdout))??
.lines()
.any(|line| line.starts_with(REQUIRED_VERSION));
if !is_installed {
let exit_status = Command::new("rustup")
.args(["toolchain", "install", REQUIRED_VERSION])
.spawn()?
.wait()?;
if !exit_status.success() {
return Err(anyhow!(
"Installation of `rustc {REQUIRED_VERSION}` failed. \
`rustc <1.80` is required to install Anchor v{version} from source. \
See https://github.com/coral-xyz/anchor/pull/3143 for more information."
));
));
}
}
}

// Prepend the toolchain to use with the `cargo install` command
args.insert(0, format!("+{REQUIRED_VERSION}"));
}
// Prepend the toolchain to use with the `cargo install` command
args.insert(0, format!("+{REQUIRED_VERSION}"));
}

let output = Command::new("cargo")
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.map_err(|e| anyhow!("Cargo install for {version} failed: {e}"))?;
if !output.status.success() {
return Err(anyhow!(
"Failed to install {version}, is it a valid version?"
));
}
let output = Command::new("cargo")
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.map_err(|e| anyhow!("`cargo install` for version `{version}` failed: {e}"))?;
if !output.status.success() {
return Err(anyhow!(
"Failed to install {version}, is it a valid version?"
));
}

let bin_dir = get_bin_dir_path();
let bin_name = if cfg!(target_os = "windows") {
"anchor.exe"
let bin_dir = get_bin_dir_path();
let bin_name = if cfg!(target_os = "windows") {
"anchor.exe"
} else {
"anchor"
};
fs::rename(bin_dir.join(bin_name), version_binary_path(&version))?;
} else {
"anchor"
};
fs::rename(
bin_dir.join(bin_name),
bin_dir.join(format!("anchor-{version}")),
)?;
let output = Command::new("rustc").arg("-vV").output()?;
let target = core::str::from_utf8(&output.stdout)?
.lines()
.find(|line| line.starts_with("host:"))
.and_then(|line| line.split(':').last())
.ok_or_else(|| anyhow!("`host` not found from `rustc -vV` output"))?
.trim();
let ext = if cfg!(target_os = "windows") {
".exe"
} else {
""
};
let res = reqwest::blocking::get(format!(
"https://github.com/coral-xyz/anchor/releases/download/v{version}/anchor-{version}-{target}{ext}"
))?;
if !res.status().is_success() {
return Err(anyhow!(
"Failed to download the binary for version `{version}` (status code: {})",
res.status()
));
}

let bin_path = version_binary_path(&version);
fs::write(&bin_path, res.bytes()?)?;

// Set file to executable on UNIX
#[cfg(not(target_os = "windows"))]
fs::set_permissions(
bin_path,
<fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o775),
)?;
}

// If .version file is empty or not parseable, write the newly installed version to it
if current_version().is_err() {
Expand All @@ -255,7 +290,7 @@ pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()>

/// Remove an installed version of anchor-cli
pub fn uninstall_version(version: &Version) -> Result<()> {
let version_path = get_bin_dir_path().join(format!("anchor-{version}"));
let version_path = version_binary_path(version);
if !version_path.exists() {
return Err(anyhow!("anchor-cli {} is not installed", version));
}
Expand Down
6 changes: 5 additions & 1 deletion avm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub enum Commands {
/// Flag to force installation even if the version
/// is already installed
force: bool,
#[clap(long)]
/// Build from source code rather than downloading prebuilt binaries
from_source: bool,
},
#[clap(about = "Uninstall a version of Anchor")]
Uninstall {
Expand Down Expand Up @@ -77,7 +80,8 @@ pub fn entry(opts: Cli) -> Result<()> {
Commands::Install {
version_or_commit,
force,
} => avm::install_version(version_or_commit, force),
from_source,
} => avm::install_version(version_or_commit, force, from_source),
Commands::Uninstall { version } => avm::uninstall_version(&version),
Commands::List {} => avm::list_versions(),
Commands::Update {} => avm::update(),
Expand Down
27 changes: 14 additions & 13 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,16 +556,16 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC

let cfg = Config::discover(cfg_override)?;
if let Some(cfg) = cfg {
fn parse_version(text: &str) -> String {
Regex::new(r"(\d+\.\d+\.\S+)")
.unwrap()
.captures_iter(text)
.next()
.unwrap()
.get(0)
.unwrap()
.as_str()
.to_string()
fn parse_version(text: &str) -> Option<String> {
Some(
Regex::new(r"(\d+\.\d+\.\S+)")
.unwrap()
.captures_iter(text)
.next()?
.get(0)?
.as_str()
.to_string(),
)
}

fn get_current_version(cmd_name: &str) -> Result<String> {
Expand All @@ -577,8 +577,8 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
}

let output_version = std::str::from_utf8(&output.stdout)?;
let version = parse_version(output_version);
Ok(version)
parse_version(output_version)
.ok_or_else(|| anyhow!("Failed to parse the version of `{cmd_name}`"))
}

if let Some(solana_version) = &cfg.toolchain.solana_version {
Expand Down Expand Up @@ -635,7 +635,8 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
// Hide the installation progress if the version is already installed
let is_installed = std::str::from_utf8(&output.stdout)?
.lines()
.any(|line| parse_version(line) == version);
.filter_map(parse_version)
.any(|line_version| line_version == version);
let (stderr, stdout) = if is_installed {
(Stdio::null(), Stdio::null())
} else {
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/docs/intro-to-solana.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The first point means that even if in theory the program may read and write to a

> This design is partly responsible for Solana’s high throughput. The runtime can look at all the incoming transactions of a program (and even across programs) and can check whether the memory regions in the first argument of the transactions overlap. If they don’t, the runtime can run these transactions in parallel because they don’t conflict with each other. Even better, if the runtime sees that two transactions access overlapping memory regions but only read and don’t write, it can also parallelize those transactions because they do not conflict with each other.
How exactly can a transaction specify a memory region/account? To answer that, we need to look deeper into what properties an account has ([docs here](https://docs.rs/solana-program/latest/solana_program/account_info/struct.AccountInfo.html)). This is the data structure for an account in a transaction. The `is_signer` and `is_writable` fields are set per transaction (e.g. `is_signer` is set if the corresponding private key of the account's `key` field signed the transaction) and are not part of the metadata that is saved in the heap. In front of the user data that the account can store (in the `data` field) , there is some metadata connected to each account. First, it has a key property which is an ed25519 public key and serves as the address of the account. This is how the transaction can specify which accounts the program may access in the transaction.
How exactly can a transaction specify a memory region/account? To answer that, we need to look deeper into what properties an account has ([docs here](https://docs.rs/solana-account-info/latest/solana_account_info/struct.AccountInfo.html)). This is the data structure for an account in a transaction. The `is_signer` and `is_writable` fields are set per transaction (e.g. `is_signer` is set if the corresponding private key of the account's `key` field signed the transaction) and are not part of the metadata that is saved in the heap. In front of the user data that the account can store (in the `data` field) , there is some metadata connected to each account. First, it has a key property which is an ed25519 public key and serves as the address of the account. This is how the transaction can specify which accounts the program may access in the transaction.

![Transaction](/transaction.svg)

Expand Down

0 comments on commit 9abc66a

Please sign in to comment.