Skip to content

Commit d17fc3e

Browse files
authored
When updating versions in Cargo.tomls, also update dependencies (Azure#2342)
* When updating versions in Cargo.tomls, also update depedenceies If another package takes a path and version depedency on the package being updated, replace the version in the dendency as well * Use `\n` instead of `^` for start of line * using toml * Revert cargo.toml change * using toml_edit * Fix update-pathversions.rs * Install nightly for pack * Remember to uncomment out debug lines * Revert cargo.toml changes * Install rust nightly before bumping versions * Address PR feedback * clean up script * Check exit code on cargo statements * No need to group output from cargo commands * Remove `--locked` from `cargo publish` call * Use main for auto-PR targets from PR builds * Add exception for azure_identity
1 parent 6422372 commit d17fc3e

File tree

6 files changed

+184
-10
lines changed

6 files changed

+184
-10
lines changed

eng/pipelines/templates/jobs/pack.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ jobs:
3030
Write-Host '##vso[task.addattachment type=AdditionalTags;name=AdditionalTags;]$(System.DefaultWorkingDirectory)/build-reason.json'
3131
displayName: Tag build reason
3232
33+
- template: /eng/pipelines/templates/steps/use-rust.yml@self
34+
parameters:
35+
Toolchain: nightly
36+
3337
- template: /eng/pipelines/templates/steps/use-rust.yml@self
3438
parameters:
3539
Toolchain: stable

eng/pipelines/templates/stages/archetype-rust-release.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ stages:
3030
- template: /eng/pipelines/templates/variables/globals.yml
3131
- template: /eng/pipelines/templates/variables/image.yml
3232
- template: /eng/pipelines/templates/variables/rust.yml
33-
33+
3434
pool:
3535
name: $(LINUXPOOL)
3636
image: $(LINUXVMIMAGE)
@@ -49,11 +49,11 @@ stages:
4949
artifact: ${{parameters.PipelineArtifactName}}
5050

5151
- template: /eng/common/pipelines/templates/steps/retain-run.yml
52-
52+
5353
- script: |
5454
echo "##vso[build.addbuildtag]${{artifact.name}}"
5555
displayName: Add build tag '${{artifact.name}}'
56-
56+
5757
- template: /eng/common/pipelines/templates/steps/create-tags-and-git-release.yml
5858
parameters:
5959
ArtifactLocation: $(Pipeline.Workspace)/${{parameters.PipelineArtifactName}}/${{artifact.name}}
@@ -129,6 +129,10 @@ stages:
129129
paths:
130130
- "/*"
131131

132+
- template: /eng/pipelines/templates/steps/use-rust.yml@self
133+
parameters:
134+
Toolchain: nightly
135+
132136
# Apply the version increment to each library, which updates the Cargo.toml and changelog files.
133137
- task: PowerShell@2
134138
displayName: Increment ${{artifact.name}} version
@@ -145,6 +149,8 @@ stages:
145149
CommitMsg: "Increment package version after release of ${{ artifact.name }}"
146150
PRTitle: "Increment versions for ${{parameters.ServiceDirectory}} releases"
147151
CloseAfterOpenForTesting: '${{parameters.TestPipeline}}'
152+
${{ if startsWith(variables['Build.SourceBranch'], 'refs/pull/') }}:
153+
BaseBranchName: main
148154

149155
- ${{ if eq(parameters.TestPipeline, true) }}:
150156
- job: YankCrates
@@ -153,11 +159,11 @@ stages:
153159
condition: and(succeeded(), ne(variables['Skip.PublishPackage'], 'true'))
154160
steps:
155161
- template: /eng/common/pipelines/templates/steps/sparse-checkout.yml
156-
162+
157163
- download: current
158164
displayName: Download ${{parameters.PipelineArtifactName}} artifact
159165
artifact: ${{parameters.PipelineArtifactName}}
160-
166+
161167
- task: PowerShell@2
162168
displayName: Yank Crates
163169
env:

eng/scripts/Language-Settings.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function Get-AllPackageInfoFromRepo ([string] $ServiceDirectory) {
4545

4646
# when a package is marked `publish = false` in the Cargo.toml, `cargo metadata` returns an empty array for
4747
# `publish`, otherwise it returns null. We only want to include packages where `publish` is null.
48-
$packages = cargo metadata --format-version 1
48+
$packages = Invoke-LoggedCommand "cargo metadata --format-version 1 --no-deps" -GroupOutput
4949
| ConvertFrom-Json -AsHashtable
5050
| Select-Object -ExpandProperty packages
5151
| Where-Object { $_.manifest_path.StartsWith($searchPath) -and $null -eq $_.publish }

eng/scripts/Publish-Crates.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ foreach ($crateName in $CrateNames) {
1414
Write-Host "Publishing packae: '$crateName'"
1515
$manifestPath = "$PackagesPath/$crateName/Cargo.toml"
1616
# https://doc.rust-lang.org/cargo/reference/registry-web-api.html#publish
17-
Write-Host "> cargo publish --locked --manifest-path `"$manifestPath`""
18-
cargo publish --locked --manifest-path $manifestPath
17+
Write-Host "> cargo publish --manifest-path `"$manifestPath`""
18+
cargo publish --manifest-path $manifestPath
1919
if (!$?) {
2020
Write-Error "Failed to publish package: '$crateName'"
2121
exit 1

eng/scripts/Update-PackageVersion.ps1

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,11 @@ if ($content -ne $updated) {
8181
$updated | Set-Content -Path $tomlPath -Encoding utf8 -NoNewLine
8282
Write-Host "Updated version in $tomlPath from $($pkgProperties.Version) to $packageSemVer."
8383

84-
cargo metadata --format-version 1 | Out-Null
85-
Write-Host "Updated Cargo.lock using 'cargo metadata'."
84+
Write-Host "Updaging dependencies in Cargo.toml files."
85+
Invoke-LoggedCommand "cargo +nightly -Zscript '$RepoRoot/eng/scripts/update-pathversions.rs' update" | Out-Null
86+
87+
Write-Host "Updating Cargo.lock using 'cargo metadata'."
88+
Invoke-LoggedCommand "cargo metadata --no-deps --format-version 1" | Out-Null
8689
}
8790
else {
8891
Write-Host "$tomlPath already contains version $packageSemVer"

eng/scripts/update-pathversions.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env -S cargo +nightly -Zscript
2+
---
3+
[package]
4+
edition = "2021"
5+
description = "In all Cargo.toml files in the repo, for all dependencies that have both path and version properties, update the version property to the version in the package."
6+
7+
[dependencies]
8+
regex = "1.5"
9+
toml_edit = "0.22"
10+
---
11+
12+
use regex::Regex;
13+
use std::io::Write;
14+
use std::{env, error::Error, fs, path::PathBuf};
15+
use toml_edit::{value, DocumentMut, Item, Table};
16+
17+
fn main() -> Result<(), Box<dyn std::error::Error>> {
18+
let add_mode = env::args().nth(1)
19+
.map(|arg| match arg.as_str() {
20+
"add" => true,
21+
"update" => false,
22+
_ => panic!("Invalid mode. Use 'add' or 'update'.")
23+
})
24+
.expect("requires 'add' or 'update' mode argument");
25+
26+
let script_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
27+
let repo_root = script_root.join("../../..").canonicalize()?;
28+
29+
// find all Cargo.toml files in the repo_root directory
30+
let exclude_dirs = vec![
31+
repo_root.join("eng"),
32+
repo_root.join("target")
33+
];
34+
35+
let toml_files = load_cargo_toml_files(&repo_root, &exclude_dirs)?;
36+
37+
let package_versions = get_package_versions(&toml_files);
38+
39+
for mut toml_file in toml_files {
40+
let should_add = add_mode && !toml_file.is_publish_disabled;
41+
42+
update_package_versions(toml_file.document.as_table_mut(), &package_versions, should_add);
43+
44+
// if the toml file has a workspace table, update the workspace table
45+
if let Some(workspace) = toml_file.document.get_mut("workspace") {
46+
if let Some(table) = workspace.as_table_mut() {
47+
update_package_versions(table, &package_versions, should_add);
48+
}
49+
}
50+
51+
// write the updated document back to the file
52+
let mut file = fs::File::create(toml_file.path)?;
53+
fs::File::write_all(&mut file, toml_file.document.to_string().as_bytes())?;
54+
}
55+
56+
Ok(())
57+
}
58+
59+
fn load_cargo_toml_files(repo_root: &PathBuf, exclude_dirs: &Vec<PathBuf>) -> Result<Vec<TomlInfo>, Box<dyn Error>> {
60+
let mut toml_paths = Vec::new();
61+
find_cargo_toml_files(repo_root, exclude_dirs, &mut toml_paths)?;
62+
63+
let mut toml_files = Vec::new();
64+
for path in toml_paths {
65+
let content = fs::read_to_string(&path)?;
66+
let doc = content.parse::<DocumentMut>()?;
67+
let package_table = doc.get("package").and_then(Item::as_table);
68+
let publish_property = package_table.and_then(|table| table.get("publish")).and_then(Item::as_bool);
69+
let package_name = package_table.and_then(|table| table.get("name")).and_then(Item::as_str);
70+
let package_version = package_table.and_then(|table| table.get("version")).and_then(Item::as_str);
71+
72+
toml_files.push(TomlInfo {
73+
path,
74+
package_name: package_name.map(|s| s.to_string()),
75+
package_version: package_version.map(|s| s.to_string()),
76+
is_publish_disabled: publish_property == Some(false),
77+
document: doc
78+
});
79+
}
80+
81+
Ok(toml_files)
82+
}
83+
84+
fn find_cargo_toml_files(dir: &PathBuf, exclude_dirs: &Vec<PathBuf>, toml_paths: &mut Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
85+
for entry in fs::read_dir(dir)? {
86+
let entry = entry?;
87+
let path = entry.path();
88+
if path.is_dir() && !exclude_dirs.contains(&path) {
89+
find_cargo_toml_files(&path, exclude_dirs, toml_paths)?;
90+
} else if path.is_file() && path.file_name() == Some("Cargo.toml".as_ref()) {
91+
toml_paths.push(path);
92+
}
93+
}
94+
95+
Ok(())
96+
}
97+
98+
fn get_package_versions(toml_files: &Vec<TomlInfo>) -> Vec<(String, String, bool)> {
99+
let mut package_versions = Vec::new();
100+
101+
for toml_file in toml_files {
102+
if toml_file.package_name.is_none() || toml_file.package_version.is_none() {
103+
continue;
104+
}
105+
106+
package_versions.push((toml_file.package_name.clone().unwrap(), toml_file.package_version.clone().unwrap(), toml_file.is_publish_disabled));
107+
}
108+
109+
package_versions
110+
}
111+
112+
fn update_package_versions(toml: &mut Table, package_versions: &Vec<(String, String, bool)>, add: bool) {
113+
// for each dependency table, for each package in package_versions
114+
// if the package is in the dependency table
115+
// if the dependency has both path and version properties, update the version property
116+
// if the dependency has has path, but not version, add the version property only if
117+
// 1. the table name is not "dev-dependencies"
118+
// 2. the package is not publish disabled
119+
// 3. the add flag is true
120+
121+
let dependency_tables = get_dependency_tables(toml);
122+
123+
for (table_name, table) in dependency_tables {
124+
for (package, version, is_publish_disabled) in package_versions {
125+
if let Some(dependency) = table.get_mut(package) {
126+
// azure_idenentity will only be a transitive dev-dependency
127+
let should_add = add && table_name != "dev-dependencies" && !is_publish_disabled && package != "azure_identity";
128+
129+
let has_path_property = dependency.get("path").is_some();
130+
let has_version_property = dependency.get("version").is_some();
131+
132+
if has_path_property && (has_version_property || should_add) {
133+
dependency["version"] = value(version);
134+
}
135+
}
136+
}
137+
}
138+
}
139+
140+
fn get_dependency_tables(toml: &mut Table) -> Vec<(String, &mut Table)> {
141+
let re = Regex::new(r"[.-]?dependencies$").unwrap();
142+
let mut tables = Vec::new();
143+
144+
for (key, value) in toml.iter_mut() {
145+
if let Some(table) = value.as_table_mut() {
146+
if re.is_match(&key) {
147+
tables.push((key.to_string(), table));
148+
}
149+
}
150+
}
151+
152+
tables
153+
}
154+
155+
struct TomlInfo {
156+
path: PathBuf,
157+
package_name: Option<String>,
158+
package_version: Option<String>,
159+
is_publish_disabled: bool,
160+
document: DocumentMut,
161+
}

0 commit comments

Comments
 (0)